import {
  BadRequestException,
  Injectable,
  NotFoundException,
} from "@nestjs/common";
import { InjectRepository } from "@nestjs/typeorm";
import { Repository } from "typeorm";
import { CreateCatalogoVehicularDto } from "./dto/create-catalogo-vehicular.dto";
import { CosaTrae } from "./entities/cosa-trae.entity";
import {
  CatalogoVehicular,
  CatalogoVehicularTipo,
} from "./entities/catalogo-vehicular.entity";
import { TipoDano } from "./entities/tipo-dano.entity";
import { TipoVehiculo } from "./entities/tipo-vehiculo.entity";

@Injectable()
export class CatalogosService {
  constructor(
    @InjectRepository(TipoVehiculo)
    private tipoVehiculoRepo: Repository<TipoVehiculo>,
    @InjectRepository(CosaTrae)
    private cosaTraeRepo: Repository<CosaTrae>,
    @InjectRepository(TipoDano)
    private tipoDanoRepo: Repository<TipoDano>,
    @InjectRepository(CatalogoVehicular)
    private catalogoVehicularRepo: Repository<CatalogoVehicular>,
  ) {}

  private normalize(value?: string | null): string {
    return value?.trim() || "";
  }

  private normalizeKey(value?: string | null): string {
    return this.normalize(value).toLowerCase();
  }

  private ensureTipoVehiculoId(value?: number | null): number {
    const n = Number(value);
    if (!Number.isInteger(n) || n <= 0) {
      throw new BadRequestException("tipoVehiculoId es obligatorio");
    }
    return n;
  }

  private async ensureTipoVehiculoActivo(
    tipoVehiculoId: number,
  ): Promise<void> {
    const tipo = await this.tipoVehiculoRepo.findOne({
      where: { id: tipoVehiculoId, activo: true },
    });
    if (!tipo) {
      throw new BadRequestException(
        "El tipo de vehiculo seleccionado no existe o esta inactivo",
      );
    }
  }

  private async findActiveVehicular(
    tipo: CatalogoVehicularTipo,
    nombre: string,
    ctx?: {
      marca?: string;
      modelo?: string;
      motor?: string;
      tipoVehiculoId?: number;
    },
    excludeId?: number,
  ): Promise<CatalogoVehicular | null> {
    const qb = this.catalogoVehicularRepo
      .createQueryBuilder("cat")
      .where("cat.tipo = :tipo", { tipo })
      .andWhere("cat.activo = true")
      .andWhere("LOWER(cat.nombre) = :nombre", {
        nombre: this.normalizeKey(nombre),
      });

    if (ctx?.marca) {
      qb.andWhere("LOWER(cat.marca) = :marca", {
        marca: this.normalizeKey(ctx.marca),
      });
    }

    if (ctx?.modelo) {
      qb.andWhere("LOWER(cat.modelo) = :modelo", {
        modelo: this.normalizeKey(ctx.modelo),
      });
    }

    if (ctx?.motor) {
      qb.andWhere("LOWER(cat.motor) = :motor", {
        motor: this.normalizeKey(ctx.motor),
      });
    }

    if (ctx?.tipoVehiculoId) {
      qb.andWhere("cat.tipoVehiculoId = :tipoVehiculoId", {
        tipoVehiculoId: ctx.tipoVehiculoId,
      });
    }

    if (excludeId) {
      qb.andWhere("cat.id <> :excludeId", { excludeId });
    }

    return qb.orderBy("cat.id", "DESC").getOne();
  }

  private async hasActiveChildren(
    tipo: CatalogoVehicularTipo,
    ctx: {
      marca?: string;
      modelo?: string;
      motor?: string;
      tipoVehiculoId?: number;
    },
  ): Promise<boolean> {
    const qb = this.catalogoVehicularRepo
      .createQueryBuilder("cat")
      .where("cat.tipo = :tipo", { tipo })
      .andWhere("cat.activo = true");

    if (ctx.marca) {
      qb.andWhere("LOWER(cat.marca) = :marca", {
        marca: this.normalizeKey(ctx.marca),
      });
    }

    if (ctx.modelo) {
      qb.andWhere("LOWER(cat.modelo) = :modelo", {
        modelo: this.normalizeKey(ctx.modelo),
      });
    }

    if (ctx.motor) {
      qb.andWhere("LOWER(cat.motor) = :motor", {
        motor: this.normalizeKey(ctx.motor),
      });
    }

    if (ctx.tipoVehiculoId) {
      qb.andWhere("cat.tipoVehiculoId = :tipoVehiculoId", {
        tipoVehiculoId: ctx.tipoVehiculoId,
      });
    }

    return (await qb.getCount()) > 0;
  }

  private async resolveVehicularPayload(
    dto: Partial<CreateCatalogoVehicularDto>,
    tipoBase: CatalogoVehicularTipo,
    existing?: CatalogoVehicular,
  ) {
    const nombre = this.normalize(dto.nombre ?? existing?.nombre);
    if (!nombre) {
      throw new BadRequestException("El nombre es obligatorio");
    }

    const tipoVehiculoId = this.ensureTipoVehiculoId(
      dto.tipoVehiculoId ?? existing?.tipoVehiculoId ?? undefined,
    );
    await this.ensureTipoVehiculoActivo(tipoVehiculoId);

    const marcaInput = this.normalize(dto.marca ?? existing?.marca);
    const modeloInput = this.normalize(dto.modelo ?? existing?.modelo);
    const motorInput = this.normalize(dto.motor ?? existing?.motor);

    if (tipoBase === CatalogoVehicularTipo.MARCA) {
      const dup = await this.findActiveVehicular(
        CatalogoVehicularTipo.MARCA,
        nombre,
        { tipoVehiculoId },
        existing?.id,
      );
      if (dup)
        throw new BadRequestException(
          "La marca ya existe para este tipo de vehiculo",
        );
      return { nombre, marca: "", modelo: "", motor: "", tipoVehiculoId };
    }

    if (tipoBase === CatalogoVehicularTipo.MODELO) {
      if (!marcaInput) {
        throw new BadRequestException(
          "Para crear modelo debes seleccionar una marca",
        );
      }

      const marca = await this.findActiveVehicular(
        CatalogoVehicularTipo.MARCA,
        marcaInput,
        { tipoVehiculoId },
      );
      if (!marca) {
        throw new BadRequestException(
          "La marca seleccionada no existe o esta inactiva para ese tipo de vehiculo",
        );
      }

      const dup = await this.findActiveVehicular(
        CatalogoVehicularTipo.MODELO,
        nombre,
        { marca: marca.nombre, tipoVehiculoId },
        existing?.id,
      );
      if (dup)
        throw new BadRequestException(
          "Ese modelo ya existe para esa marca y tipo de vehiculo",
        );

      return {
        nombre,
        marca: marca.nombre,
        modelo: "",
        motor: "",
        tipoVehiculoId,
      };
    }

    if (tipoBase === CatalogoVehicularTipo.MOTOR) {
      if (!marcaInput || !modeloInput) {
        throw new BadRequestException(
          "Para crear motor debes seleccionar marca y modelo",
        );
      }

      const modelo = await this.findActiveVehicular(
        CatalogoVehicularTipo.MODELO,
        modeloInput,
        {
          marca: marcaInput,
          tipoVehiculoId,
        },
      );
      if (!modelo) {
        throw new BadRequestException(
          "El modelo seleccionado no existe para la marca y tipo de vehiculo indicado",
        );
      }

      const dup = await this.findActiveVehicular(
        CatalogoVehicularTipo.MOTOR,
        nombre,
        { marca: modelo.marca, modelo: modelo.nombre, tipoVehiculoId },
        existing?.id,
      );
      if (dup)
        throw new BadRequestException(
          "Ese motor ya existe para esa marca, modelo y tipo de vehiculo",
        );

      return {
        nombre,
        marca: modelo.marca,
        modelo: modelo.nombre,
        motor: "",
        tipoVehiculoId,
      };
    }

    if (!marcaInput || !modeloInput || !motorInput) {
      throw new BadRequestException(
        "Para crear cilindraje debes seleccionar marca, modelo y motor",
      );
    }

    const motor = await this.findActiveVehicular(
      CatalogoVehicularTipo.MOTOR,
      motorInput,
      {
        marca: marcaInput,
        modelo: modeloInput,
        tipoVehiculoId,
      },
    );

    if (!motor) {
      throw new BadRequestException(
        "El motor seleccionado no existe para esa marca, modelo y tipo de vehiculo",
      );
    }

    const dup = await this.findActiveVehicular(
      CatalogoVehicularTipo.CILINDRAJE,
      nombre,
      {
        marca: motor.marca,
        modelo: motor.modelo,
        motor: motor.nombre,
        tipoVehiculoId,
      },
      existing?.id,
    );
    if (dup)
      throw new BadRequestException(
        "Ese cilindraje ya existe para ese motor y tipo de vehiculo",
      );

    return {
      nombre,
      marca: motor.marca,
      modelo: motor.modelo,
      motor: motor.nombre,
      tipoVehiculoId,
    };
  }

  async findAllTiposVehiculo() {
    return this.tipoVehiculoRepo.find({
      where: { activo: true },
      order: { nombre: "ASC" },
    });
  }

  async createTipoVehiculo(data: Partial<TipoVehiculo>, userId?: number) {
    const tipo = this.tipoVehiculoRepo.create({
      ...data,
      createdBy: userId ?? null,
      updatedBy: userId ?? null,
    });
    return this.tipoVehiculoRepo.save(tipo);
  }

  async updateTipoVehiculo(
    id: number,
    data: Partial<TipoVehiculo>,
    userId?: number,
  ) {
    const tipo = await this.tipoVehiculoRepo.findOne({ where: { id } });
    if (!tipo)
      throw new NotFoundException(`Tipo de vehiculo ${id} no encontrado`);
    Object.assign(tipo, data, { updatedBy: userId ?? null });
    return this.tipoVehiculoRepo.save(tipo);
  }

  async inactivateTipoVehiculo(id: number, userId?: number) {
    const tipo = await this.tipoVehiculoRepo.findOne({ where: { id } });
    if (!tipo)
      throw new NotFoundException(`Tipo de vehiculo ${id} no encontrado`);
    tipo.activo = false;
    tipo.updatedBy = userId ?? null;
    return this.tipoVehiculoRepo.save(tipo);
  }

  async findAllCosasTrae() {
    return this.cosaTraeRepo.find({
      where: { activo: true },
      order: { nombre: "ASC" },
    });
  }

  async createCosaTrae(data: Partial<CosaTrae>) {
    const cosa = this.cosaTraeRepo.create(data);
    return this.cosaTraeRepo.save(cosa);
  }

  async updateCosaTrae(id: number, data: Partial<CosaTrae>) {
    const cosa = await this.cosaTraeRepo.findOne({ where: { id } });
    if (!cosa) throw new NotFoundException(`Cosa que trae ${id} no encontrada`);
    Object.assign(cosa, data);
    return this.cosaTraeRepo.save(cosa);
  }

  async inactivateCosaTrae(id: number) {
    const cosa = await this.cosaTraeRepo.findOne({ where: { id } });
    if (!cosa) throw new NotFoundException(`Cosa que trae ${id} no encontrada`);
    cosa.activo = false;
    return this.cosaTraeRepo.save(cosa);
  }

  async findAllTiposDano() {
    return this.tipoDanoRepo.find({
      where: { activo: true },
      order: { nombre: "ASC" },
    });
  }

  async createTipoDano(data: Partial<TipoDano>) {
    const tipo = this.tipoDanoRepo.create(data);
    return this.tipoDanoRepo.save(tipo);
  }

  async updateTipoDano(id: number, data: Partial<TipoDano>) {
    const tipo = await this.tipoDanoRepo.findOne({ where: { id } });
    if (!tipo) throw new NotFoundException(`Tipo de dano ${id} no encontrado`);
    Object.assign(tipo, data);
    return this.tipoDanoRepo.save(tipo);
  }

  async inactivateTipoDano(id: number) {
    const tipo = await this.tipoDanoRepo.findOne({ where: { id } });
    if (!tipo) throw new NotFoundException(`Tipo de dano ${id} no encontrado`);
    tipo.activo = false;
    return this.tipoDanoRepo.save(tipo);
  }

  async listCatalogosVehiculares(
    tipo: CatalogoVehicularTipo,
    filters?: {
      marca?: string;
      modelo?: string;
      motor?: string;
      tipoVehiculoId?: number;
    },
  ) {
    const qb = this.catalogoVehicularRepo
      .createQueryBuilder("cat")
      .where("cat.tipo = :tipo", { tipo })
      .andWhere("cat.activo = true");

    if (filters?.marca)
      qb.andWhere("LOWER(cat.marca) = :marca", {
        marca: this.normalizeKey(filters.marca),
      });
    if (filters?.modelo)
      qb.andWhere("LOWER(cat.modelo) = :modelo", {
        modelo: this.normalizeKey(filters.modelo),
      });
    if (filters?.motor)
      qb.andWhere("LOWER(cat.motor) = :motor", {
        motor: this.normalizeKey(filters.motor),
      });
    if (filters?.tipoVehiculoId)
      qb.andWhere("cat.tipoVehiculoId = :tipoVehiculoId", {
        tipoVehiculoId: filters.tipoVehiculoId,
      });

    return qb.orderBy("cat.nombre", "ASC").getMany();
  }

  async createCatalogoVehicular(dto: CreateCatalogoVehicularDto) {
    const payload = await this.resolveVehicularPayload(dto, dto.tipo);

    const row = this.catalogoVehicularRepo.create({
      tipo: dto.tipo,
      nombre: payload.nombre,
      marca: payload.marca,
      modelo: payload.modelo,
      motor: payload.motor,
      tipoVehiculoId: payload.tipoVehiculoId,
      activo: true,
    } as Partial<CatalogoVehicular>);

    return this.catalogoVehicularRepo.save(row);
  }

  async updateCatalogoVehicular(
    id: number,
    dto: Partial<CreateCatalogoVehicularDto>,
  ) {
    const row = await this.catalogoVehicularRepo.findOne({ where: { id } });
    if (!row)
      throw new NotFoundException(`Catalogo vehicular ${id} no encontrado`);

    if (dto.tipo && dto.tipo !== row.tipo) {
      throw new BadRequestException("No se puede cambiar el tipo del registro");
    }

    const payload = await this.resolveVehicularPayload(dto, row.tipo, row);

    row.nombre = payload.nombre;
    row.marca = payload.marca;
    row.modelo = payload.modelo;
    row.motor = payload.motor;
    row.tipoVehiculoId = payload.tipoVehiculoId;

    return this.catalogoVehicularRepo.save(row);
  }

  async inactivateCatalogoVehicular(id: number) {
    const row = await this.catalogoVehicularRepo.findOne({ where: { id } });
    if (!row)
      throw new NotFoundException(`Catalogo vehicular ${id} no encontrado`);

    if (row.tipo === CatalogoVehicularTipo.MARCA) {
      const hasChildren = await this.hasActiveChildren(
        CatalogoVehicularTipo.MODELO,
        {
          marca: row.nombre,
          tipoVehiculoId: row.tipoVehiculoId ?? undefined,
        },
      );
      if (hasChildren) {
        throw new BadRequestException(
          "No puedes inactivar una marca que tiene modelos activos",
        );
      }
    }

    if (row.tipo === CatalogoVehicularTipo.MODELO) {
      const hasChildren = await this.hasActiveChildren(
        CatalogoVehicularTipo.MOTOR,
        {
          marca: row.marca,
          modelo: row.nombre,
          tipoVehiculoId: row.tipoVehiculoId ?? undefined,
        },
      );
      if (hasChildren) {
        throw new BadRequestException(
          "No puedes inactivar un modelo que tiene motores activos",
        );
      }
    }

    if (row.tipo === CatalogoVehicularTipo.MOTOR) {
      const hasChildren = await this.hasActiveChildren(
        CatalogoVehicularTipo.CILINDRAJE,
        {
          marca: row.marca,
          modelo: row.modelo,
          motor: row.nombre,
          tipoVehiculoId: row.tipoVehiculoId ?? undefined,
        },
      );
      if (hasChildren) {
        throw new BadRequestException(
          "No puedes inactivar un motor que tiene cilindrajes activos",
        );
      }
    }

    row.activo = false;
    return this.catalogoVehicularRepo.save(row);
  }

  async seedCatalogos() {
    const tiposCount = await this.tipoVehiculoRepo.count();
    if (tiposCount === 0) {
      await this.tipoVehiculoRepo.save([
        { nombre: "Carro", plantillaDanos: "carro" },
        { nombre: "Moto", plantillaDanos: "moto" },
        { nombre: "Camion", plantillaDanos: "camion" },
        { nombre: "Pickup", plantillaDanos: "carro" },
        { nombre: "SUV", plantillaDanos: "carro" },
      ]);
    }

    const cosasCount = await this.cosaTraeRepo.count();
    if (cosasCount === 0) {
      await this.cosaTraeRepo.save([
        { nombre: "Radio" },
        { nombre: "Gata" },
        { nombre: "Herramientas" },
        { nombre: "Llanta de repuesto" },
        { nombre: "Triangulos" },
        { nombre: "Extintor" },
      ]);
    }

    const danosCount = await this.tipoDanoRepo.count();
    if (danosCount === 0) {
      await this.tipoDanoRepo.save([
        { nombre: "Golpe", icono: "golpe", color: "#ef4444" },
        { nombre: "Rayon", icono: "rayon", color: "#f59e0b" },
        { nombre: "Quebrado", icono: "quebrado", color: "#dc2626" },
      ]);
    }

    return { ok: true };
  }
}
