import {
  BadRequestException,
  Injectable,
  NotFoundException,
} from "@nestjs/common";
import { InjectRepository } from "@nestjs/typeorm";
import { Repository } from "typeorm";
import { ProductosService } from "../productos/productos.service";
import { AddLineaDto } from "./dto/add-linea.dto";
import { CreateCotizacionDto } from "./dto/create-cotizacion.dto";
import { UpdateLineaDto } from "./dto/update-linea.dto";
import { CotizacionLinea } from "./entities/cotizacion-linea.entity";
import { Cotizacion, EstadoCotizacion } from "./entities/cotizacion.entity";

@Injectable()
export class CotizacionesService {
  constructor(
    @InjectRepository(Cotizacion)
    private cotizacionRepository: Repository<Cotizacion>,
    @InjectRepository(CotizacionLinea)
    private lineaRepository: Repository<CotizacionLinea>,
    private productosService: ProductosService,
  ) {}

  async findByOt(otId: number): Promise<Cotizacion | null> {
    return this.cotizacionRepository.findOne({
      where: { otId },
      relations: ["lineas"],
    });
  }

  async findById(id: number): Promise<Cotizacion> {
    const cotizacion = await this.cotizacionRepository.findOne({
      where: { id },
      relations: ["lineas"],
    });

    if (!cotizacion) {
      throw new NotFoundException(`Cotizacion ${id} no encontrada`);
    }

    return cotizacion;
  }

  async create(dto: CreateCotizacionDto): Promise<Cotizacion> {
    const existing = await this.findByOt(dto.otId);
    if (existing) {
      return existing;
    }

    const numeroCotizacion = await this.generateNumeroCotizacion();

    const cotizacion = this.cotizacionRepository.create({
      otId: dto.otId,
      numeroCotizacion,
      notas: dto.notas,
      localId: dto.localId,
    });

    return this.cotizacionRepository.save(cotizacion);
  }

  async addLinea(
    cotizacionId: number,
    dto: AddLineaDto,
  ): Promise<CotizacionLinea> {
    const cotizacion = await this.findById(cotizacionId);

    if (cotizacion.estado === EstadoCotizacion.APROBADA) {
      throw new BadRequestException(
        "No se pueden agregar lineas a una cotizacion aprobada",
      );
    }

    const producto = await this.productosService.findByPlu(dto.plu);

    const cantidad = dto.cantidad || 1;
    const precioUnitario = producto.precio;
    const subtotal = precioUnitario * cantidad;
    const iva = producto.pagaiva ? subtotal * (Number(producto.iva || 0) / 100) : 0;
    const total = subtotal + iva;

    const linea = this.lineaRepository.create({
      cotizacionId,
      plu: producto.plu,
      descripcion: producto.desclarga || producto.desccorta || producto.plu,
      precioUnitario,
      cantidad,
      subtotal,
      iva,
      total,
      esServicio: producto.es_servicio,
      orden: cotizacion.lineas ? cotizacion.lineas.length : 0,
    });

    const savedLinea = await this.lineaRepository.save(linea);

    await this.recalculateTotals(cotizacionId);

    return savedLinea;
  }

  async updateLinea(
    cotizacionId: number,
    lineaId: number,
    dto: UpdateLineaDto,
  ): Promise<CotizacionLinea> {
    const cotizacion = await this.findById(cotizacionId);

    if (cotizacion.estado === EstadoCotizacion.APROBADA) {
      throw new BadRequestException(
        "No se pueden modificar lineas de una cotizacion aprobada",
      );
    }

    const linea = await this.lineaRepository.findOne({
      where: { id: lineaId, cotizacionId },
    });

    if (!linea) {
      throw new NotFoundException(`Linea ${lineaId} no encontrada`);
    }

    if (dto.cantidad !== undefined) {
      const previousSubtotal = Number(linea.subtotal || 0);
      const previousIva = Number(linea.iva || 0);
      const ivaRate = previousSubtotal > 0 ? previousIva / previousSubtotal : 0;

      linea.cantidad = dto.cantidad;
      linea.subtotal = linea.precioUnitario * dto.cantidad;
      linea.iva = linea.subtotal * ivaRate;
      linea.total = linea.subtotal + linea.iva;
    }

    const savedLinea = await this.lineaRepository.save(linea);
    await this.recalculateTotals(cotizacionId);

    return savedLinea;
  }

  async removeLinea(cotizacionId: number, lineaId: number): Promise<void> {
    const cotizacion = await this.findById(cotizacionId);

    if (cotizacion.estado === EstadoCotizacion.APROBADA) {
      throw new BadRequestException(
        "No se pueden eliminar lineas de una cotizacion aprobada",
      );
    }

    const result = await this.lineaRepository.delete({
      id: lineaId,
      cotizacionId,
    });

    if (result.affected === 0) {
      throw new NotFoundException(`Linea ${lineaId} no encontrada`);
    }

    await this.recalculateTotals(cotizacionId);
  }

  async aprobar(
    cotizacionId: number,
    aprobadoPor: string,
  ): Promise<Cotizacion> {
    const cotizacion = await this.findById(cotizacionId);

    if (!cotizacion.lineas || cotizacion.lineas.length === 0) {
      throw new BadRequestException(
        "No se puede aprobar una cotizacion sin lineas",
      );
    }

    cotizacion.estado = EstadoCotizacion.APROBADA;
    cotizacion.fechaAprobacion = new Date();
    cotizacion.aprobadoPor = aprobadoPor;
    cotizacion.version = cotizacion.version + 1;

    return this.cotizacionRepository.save(cotizacion);
  }

  private async recalculateTotals(cotizacionId: number): Promise<void> {
    const lineas = await this.lineaRepository.find({
      where: { cotizacionId },
    });

    const subtotal = lineas.reduce((sum, l) => sum + Number(l.subtotal), 0);
    const iva = lineas.reduce((sum, l) => sum + Number(l.iva), 0);
    const total = lineas.reduce((sum, l) => sum + Number(l.total), 0);

    await this.cotizacionRepository.update(cotizacionId, {
      subtotal,
      iva,
      total,
    });
  }

  private async generateNumeroCotizacion(): Promise<string> {
    const today = new Date();
    const datePrefix = today.toISOString().slice(0, 10).replace(/-/g, "");

    const lastCot = await this.cotizacionRepository
      .createQueryBuilder("cot")
      .where("cot.numeroCotizacion LIKE :prefix", {
        prefix: `COT-${datePrefix}-%`,
      })
      .orderBy("cot.numeroCotizacion", "DESC")
      .getOne();

    let sequence = 1;

    if (lastCot && lastCot.numeroCotizacion) {
      const match = lastCot.numeroCotizacion.match(/COT-\d{8}-(\d+)/);
      if (match) {
        sequence = parseInt(match[1], 10) + 1;
      }
    }

    return `COT-${datePrefix}-${sequence.toString().padStart(4, "0")}`;
  }
}
