import { BadRequestException, ForbiddenException, Injectable, NotFoundException } from '@nestjs/common';
import { presupuesto_aprobacion_resultado, presupuesto_estado } from '@prisma/client';
import { PrismaService } from 'src/prisma/prisma.service';
import { MarkAcceptedDto } from './dto/aprobacion.dto';
import { PresupuestosService } from './presupuestos.service';

/** Estados desde los que se puede registrar la aceptación del cliente por fuera del portal. */
const ESTADOS_ACEPTABLES_INTERNO: presupuesto_estado[] = [
  presupuesto_estado.APPROVED,
  presupuesto_estado.SENT,
  presupuesto_estado.VIEWED,
];

/** Etiqueta legible del canal, para el comentario del historial. */
const CANAL_LABEL: Record<string, string> = {
  TELEFONO: 'confirmación telefónica',
  VERBAL: 'confirmación verbal',
  PRESENCIAL: 'negociación presencial',
  WHATSAPP: 'confirmación por WhatsApp',
  EMAIL: 'confirmación por email',
  OTRO: 'confirmación por otro canal',
};

@Injectable()
export class PresupuestosAprobacionService {
  constructor(
    private readonly prisma: PrismaService,
    private readonly presupuestosService: PresupuestosService,
  ) {}

  /** Nivel de aprobación (1/2/3) según el monto total y los umbrales de la config. */
  private nivelPorMonto(total: number, c: { umbral_nivel1: number; umbral_nivel2: number; umbral_nivel3: number }): number {
    if (total >= c.umbral_nivel3) return 3;
    if (total >= c.umbral_nivel2) return 2;
    return 1;
  }

  private async getCab(id: string, empresa_id: string) {
    const cab = await this.prisma.presupuesto_cab.findFirst({
      where: { id, empresa_id, deleted: false },
      select: { id: true, estado: true, total: true, numero: true },
    });
    if (!cab) throw new NotFoundException('Presupuesto no encontrado');
    return cab;
  }

  /**
   * Envía el presupuesto a aprobación interna. Si el total no supera el umbral
   * configurado, se aprueba automáticamente; si lo supera, queda IN_REVIEW con
   * un registro de aprobación pendiente en el nivel correspondiente.
   */
  async submitReview(id: string, empresa_id: string, user_id?: string) {
    const cab = await this.getCab(id, empresa_id);
    if (cab.estado !== presupuesto_estado.DRAFT) {
      throw new ForbiddenException('Sólo se puede enviar a aprobación un presupuesto en Borrador');
    }

    const config = await this.presupuestosService.getConfig(empresa_id);
    const total = Number(cab.total);
    const umbral = Number(config.monto_umbral_aprobacion);
    const requiereAprobacion = total >= umbral;

    await this.prisma.$transaction(async (tx) => {
      if (!requiereAprobacion) {
        await tx.presupuesto_cab.update({
          where: { id },
          data: { estado: presupuesto_estado.APPROVED, updated_at: new Date() },
        });
        await tx.presupuesto_historial.create({
          data: {
            presupuesto_id: id,
            estado_anterior: presupuesto_estado.DRAFT,
            estado_nuevo: presupuesto_estado.APPROVED,
            usuario_id: user_id ?? null,
            comentario: 'Aprobado automáticamente (monto por debajo del umbral)',
          },
        });
        return;
      }

      const nivel = this.nivelPorMonto(total, {
        umbral_nivel1: Number(config.umbral_nivel1),
        umbral_nivel2: Number(config.umbral_nivel2),
        umbral_nivel3: Number(config.umbral_nivel3),
      });
      // Limpiar aprobaciones pendientes previas (reenvío) y crear la del nivel actual
      await tx.presupuesto_aprobacion.deleteMany({ where: { presupuesto_id: id, resultado: null } });
      await tx.presupuesto_aprobacion.create({ data: { presupuesto_id: id, nivel } });
      await tx.presupuesto_cab.update({
        where: { id },
        data: { estado: presupuesto_estado.IN_REVIEW, updated_at: new Date() },
      });
      await tx.presupuesto_historial.create({
        data: {
          presupuesto_id: id,
          estado_anterior: presupuesto_estado.DRAFT,
          estado_nuevo: presupuesto_estado.IN_REVIEW,
          usuario_id: user_id ?? null,
          comentario: `Enviado a aprobación (nivel ${nivel})`,
        },
      });
    });

    return this.presupuestosService.findOne(id, empresa_id);
  }

  /** Un aprobador aprueba el presupuesto pendiente de revisión. */
  async approve(id: string, empresa_id: string, user_id: string, comentario?: string) {
    const cab = await this.getCab(id, empresa_id);
    if (cab.estado !== presupuesto_estado.IN_REVIEW) {
      throw new BadRequestException('El presupuesto no está pendiente de aprobación');
    }

    await this.prisma.$transaction(async (tx) => {
      await this.resolverPendiente(tx, id, user_id, presupuesto_aprobacion_resultado.APROBADO, comentario);
      await tx.presupuesto_cab.update({
        where: { id },
        data: { estado: presupuesto_estado.APPROVED, updated_at: new Date() },
      });
      await tx.presupuesto_historial.create({
        data: {
          presupuesto_id: id,
          estado_anterior: presupuesto_estado.IN_REVIEW,
          estado_nuevo: presupuesto_estado.APPROVED,
          usuario_id: user_id,
          comentario: comentario ? `Aprobado: ${comentario}` : 'Aprobado',
        },
      });
    });

    return this.presupuestosService.findOne(id, empresa_id);
  }

  /** Un aprobador rechaza la aprobación; el presupuesto vuelve a Borrador para corregirse. */
  async rejectApproval(id: string, empresa_id: string, user_id: string, motivo?: string) {
    const cab = await this.getCab(id, empresa_id);
    if (cab.estado !== presupuesto_estado.IN_REVIEW) {
      throw new BadRequestException('El presupuesto no está pendiente de aprobación');
    }

    await this.prisma.$transaction(async (tx) => {
      await this.resolverPendiente(tx, id, user_id, presupuesto_aprobacion_resultado.RECHAZADO, motivo);
      await tx.presupuesto_cab.update({
        where: { id },
        data: { estado: presupuesto_estado.DRAFT, updated_at: new Date() },
      });
      await tx.presupuesto_historial.create({
        data: {
          presupuesto_id: id,
          estado_anterior: presupuesto_estado.IN_REVIEW,
          estado_nuevo: presupuesto_estado.DRAFT,
          usuario_id: user_id,
          comentario: motivo ? `Aprobación rechazada: ${motivo}` : 'Aprobación rechazada — vuelve a Borrador',
        },
      });
    });

    return this.presupuestosService.findOne(id, empresa_id);
  }

  /**
   * Registra la aceptación del cliente cuando llegó por fuera del portal público
   * (teléfono, visita, WhatsApp). Deja el presupuesto en ACCEPTED igual que el
   * portal, pero el historial guarda el canal y quién lo confirmó — no se simula
   * una respuesta del cliente en el portal.
   */
  async markAccepted(id: string, empresa_id: string, user_id: string, dto: MarkAcceptedDto) {
    const cab = await this.prisma.presupuesto_cab.findFirst({
      where: { id, empresa_id, deleted: false },
      select: { id: true, estado: true, fecha_vigencia: true },
    });
    if (!cab) throw new NotFoundException('Presupuesto no encontrado');

    if (cab.estado === presupuesto_estado.ACCEPTED) {
      throw new BadRequestException('Este presupuesto ya está aceptado');
    }
    if (cab.estado === presupuesto_estado.DRAFT || cab.estado === presupuesto_estado.IN_REVIEW) {
      throw new BadRequestException(
        'Primero completá la aprobación interna: sólo se puede aceptar un presupuesto ya aprobado',
      );
    }
    if (!ESTADOS_ACEPTABLES_INTERNO.includes(cab.estado)) {
      throw new BadRequestException(
        `No se puede aceptar un presupuesto en estado ${cab.estado}. Creá una nueva versión para renegociarlo.`,
      );
    }

    // La vigencia se mide por día completo: un presupuesto que vence hoy sigue siendo válido.
    const hoy = new Date();
    hoy.setHours(0, 0, 0, 0);
    if (cab.fecha_vigencia && cab.fecha_vigencia < hoy) {
      throw new BadRequestException(
        'El presupuesto está vencido. Creá una nueva versión o extendé la vigencia antes de aceptarlo.',
      );
    }

    const partes = [`Aceptado internamente (${CANAL_LABEL[dto.canal] ?? dto.canal})`];
    if (dto.confirmado_por?.trim()) partes.push(`confirmó: ${dto.confirmado_por.trim()}`);
    if (dto.comentario?.trim()) partes.push(dto.comentario.trim());
    const comentario = partes.join(' — ');

    await this.prisma.$transaction(async (tx) => {
      await tx.presupuesto_cab.update({
        where: { id },
        data: {
          estado: presupuesto_estado.ACCEPTED,
          fecha_respuesta_cliente: new Date(),
          updated_at: new Date(),
        },
      });
      await tx.presupuesto_historial.create({
        data: {
          presupuesto_id: id,
          estado_anterior: cab.estado,
          estado_nuevo: presupuesto_estado.ACCEPTED,
          usuario_id: user_id,
          comentario,
        },
      });
    });

    return this.presupuestosService.findOne(id, empresa_id);
  }

  /** Marca el registro de aprobación pendiente con el resultado del aprobador. */
  private async resolverPendiente(
    tx: Parameters<Parameters<PrismaService['$transaction']>[0]>[0],
    presupuesto_id: string,
    aprobador_id: string,
    resultado: presupuesto_aprobacion_resultado,
    comentario?: string,
  ) {
    const pendiente = await tx.presupuesto_aprobacion.findFirst({
      where: { presupuesto_id, resultado: null },
      orderBy: { nivel: 'asc' },
    });
    if (pendiente) {
      await tx.presupuesto_aprobacion.update({
        where: { id: pendiente.id },
        data: { resultado, aprobador_id, comentario: comentario ?? null, fecha: new Date() },
      });
    } else {
      // No había registro pendiente (p. ej. flujo manual): crear uno resuelto.
      await tx.presupuesto_aprobacion.create({
        data: { presupuesto_id, nivel: 1, resultado, aprobador_id, comentario: comentario ?? null },
      });
    }
  }
}
