import {
  BadRequestException,
  ConflictException,
  Injectable,
  NotFoundException,
} from '@nestjs/common';
import { Prisma } from '@prisma/client';
import { PrismaService } from '../../prisma/prisma.service';
import {
  CambiarEstadoPermisoPresentismoDto,
  CreatePermisoPresentismoDto,
  PermisoPresentismoEstado,
  PermisoPresentismoTipo,
  QueryPermisosPresentismoDto,
  UpdatePermisoPresentismoDto,
} from '../dto/presentismo-fase3.dto';

function parseHora(value?: string): Date | null {
  if (!value) return null;
  const [hh, mm, ss = '0'] = value.split(':');
  const d = new Date('1970-01-01T00:00:00Z');
  d.setUTCHours(Number(hh) || 0, Number(mm) || 0, Number(ss) || 0, 0);
  return d;
}

function horaToString(value: Date | null | undefined): string | null {
  if (!value) return null;
  const hh = String(value.getUTCHours()).padStart(2, '0');
  const mm = String(value.getUTCMinutes()).padStart(2, '0');
  return `${hh}:${mm}`;
}

@Injectable()
export class PermisosPresentismoService {
  constructor(private readonly prisma: PrismaService) {}

  async listar(empresaId: string, query: QueryPermisosPresentismoDto) {
    const page = query.page ?? 1;
    const limit = query.limit ?? 50;
    const skip = (page - 1) * limit;

    const where: Prisma.rrhh_permisos_presentismoWhereInput = { empresa_id: empresaId };
    if (query.estado) where.estado = query.estado as any;
    if (query.tipo_permiso) where.tipo_permiso = query.tipo_permiso as any;
    if (query.empleado_id) where.empleado_id = query.empleado_id;
    if (query.fecha_desde || query.fecha_hasta) {
      where.fecha = {};
      if (query.fecha_desde) (where.fecha as Prisma.DateTimeFilter).gte = new Date(query.fecha_desde);
      if (query.fecha_hasta) (where.fecha as Prisma.DateTimeFilter).lte = new Date(query.fecha_hasta);
    }

    const [data, total] = await this.prisma.$transaction([
      this.prisma.rrhh_permisos_presentismo.findMany({
        where,
        skip,
        take: limit,
        orderBy: [{ fecha: 'desc' }, { created_at: 'desc' }],
        include: {
          empleado: {
            select: {
              id: true,
              numero_empleado: true,
              cedula_identidad: true,
              nombres: true,
              apellidos: true,
            },
          },
          legajo_documento: {
            select: {
              id: true,
              nombre_original: true,
              tipo_documento: { select: { codigo: true, nombre: true } },
            },
          },
        },
      }),
      this.prisma.rrhh_permisos_presentismo.count({ where }),
    ]);

    return {
      data: data.map((row) => this.serializar(row)),
      total,
      page,
      limit,
    };
  }

  async obtener(id: string, empresaId: string) {
    const row = await this.prisma.rrhh_permisos_presentismo.findFirst({
      where: { id, empresa_id: empresaId },
      include: {
        empleado: {
          select: {
            id: true,
            numero_empleado: true,
            cedula_identidad: true,
            nombres: true,
            apellidos: true,
          },
        },
        legajo_documento: {
          select: {
            id: true,
            nombre_original: true,
            tipo_documento: { select: { codigo: true, nombre: true } },
          },
        },
      },
    });
    if (!row) throw new NotFoundException('Permiso de presentismo no encontrado');
    return this.serializar(row);
  }

  async crear(empresaId: string, dto: CreatePermisoPresentismoDto, userId?: string) {
    await this.validarEmpleado(empresaId, dto.empleado_id);
    await this.validarLegajoDocumento(
      empresaId,
      dto.empleado_id,
      dto.legajo_documento_id,
      dto.tipo_justificacion,
    );
    this.validarReglasNegocio(dto);

    const data = this.mapearPayload(dto);
    const row = await this.prisma.rrhh_permisos_presentismo.create({
      data: {
        empresa_id: empresaId,
        empleado_id: dto.empleado_id,
        creado_por: userId ?? null,
        estado: 'PENDIENTE',
        ...data,
      },
      include: {
        empleado: {
          select: {
            id: true,
            numero_empleado: true,
            cedula_identidad: true,
            nombres: true,
            apellidos: true,
          },
        },
        legajo_documento: {
          select: {
            id: true,
            nombre_original: true,
            tipo_documento: { select: { codigo: true, nombre: true } },
          },
        },
      },
    });

    return this.serializar(row);
  }

  async actualizar(id: string, empresaId: string, dto: UpdatePermisoPresentismoDto) {
    const actual = await this.prisma.rrhh_permisos_presentismo.findFirst({
      where: { id, empresa_id: empresaId },
    });
    if (!actual) throw new NotFoundException('Permiso de presentismo no encontrado');
    if (actual.estado !== 'PENDIENTE') {
      throw new ConflictException('Sólo se pueden editar permisos en estado PENDIENTE');
    }

    if (dto.empleado_id) await this.validarEmpleado(empresaId, dto.empleado_id);

    const merged: CreatePermisoPresentismoDto = {
      empleado_id: dto.empleado_id ?? actual.empleado_id,
      fecha: (dto.fecha ?? actual.fecha.toISOString().slice(0, 10)) as string,
      tipo_permiso: (dto.tipo_permiso ?? actual.tipo_permiso) as PermisoPresentismoTipo,
      turno_bloque_orden: dto.turno_bloque_orden ?? actual.turno_bloque_orden ?? undefined,
      hora_desde: dto.hora_desde ?? horaToString(actual.hora_desde) ?? undefined,
      hora_hasta: dto.hora_hasta ?? horaToString(actual.hora_hasta) ?? undefined,
      minutos_tolerados: dto.minutos_tolerados ?? actual.minutos_tolerados ?? undefined,
      legajo_documento_id: dto.legajo_documento_id ?? actual.legajo_documento_id ?? undefined,
      motivo: dto.motivo ?? actual.motivo,
      tipo_justificacion: dto.tipo_justificacion ?? (actual.tipo_justificacion as any),
      afecta_descuento: dto.afecta_descuento ?? actual.afecta_descuento,
    };

    await this.validarLegajoDocumento(
      empresaId,
      merged.empleado_id,
      merged.legajo_documento_id,
      merged.tipo_justificacion,
    );
    this.validarReglasNegocio(merged);
    const mapped = this.mapearPayload(merged);

    const row = await this.prisma.rrhh_permisos_presentismo.update({
      where: { id },
      data: {
        empleado_id: merged.empleado_id,
        ...mapped,
        updated_at: new Date(),
      },
      include: {
        empleado: {
          select: {
            id: true,
            numero_empleado: true,
            cedula_identidad: true,
            nombres: true,
            apellidos: true,
          },
        },
        legajo_documento: {
          select: {
            id: true,
            nombre_original: true,
            tipo_documento: { select: { codigo: true, nombre: true } },
          },
        },
      },
    });

    return this.serializar(row);
  }

  async aprobar(id: string, empresaId: string, userId: string, _dto: CambiarEstadoPermisoPresentismoDto) {
    const row = await this.prisma.rrhh_permisos_presentismo.findFirst({
      where: { id, empresa_id: empresaId },
    });
    if (!row) throw new NotFoundException('Permiso de presentismo no encontrado');
    if (row.estado !== 'PENDIENTE') {
      throw new ConflictException(`No se puede aprobar un permiso en estado ${row.estado}`);
    }

    return this.prisma.rrhh_permisos_presentismo.update({
      where: { id },
      data: {
        estado: 'APROBADO',
        aprobado_por: userId,
        fecha_aprobacion: new Date(),
        rechazado_por: null,
        fecha_rechazo: null,
        updated_at: new Date(),
      },
    });
  }

  async rechazar(id: string, empresaId: string, userId: string, _dto: CambiarEstadoPermisoPresentismoDto) {
    const row = await this.prisma.rrhh_permisos_presentismo.findFirst({
      where: { id, empresa_id: empresaId },
    });
    if (!row) throw new NotFoundException('Permiso de presentismo no encontrado');
    if (row.estado !== 'PENDIENTE') {
      throw new ConflictException(`No se puede rechazar un permiso en estado ${row.estado}`);
    }

    return this.prisma.rrhh_permisos_presentismo.update({
      where: { id },
      data: {
        estado: 'RECHAZADO',
        rechazado_por: userId,
        fecha_rechazo: new Date(),
        aprobado_por: null,
        fecha_aprobacion: null,
        updated_at: new Date(),
      },
    });
  }

  private async validarEmpleado(empresaId: string, empleadoId: string) {
    const empleado = await this.prisma.rrhh_empleados.findFirst({
      where: { id: empleadoId, empresa_id: empresaId },
      select: { id: true },
    });
    if (!empleado) {
      throw new BadRequestException('empleado_id inválido para esta empresa');
    }
  }

  private async validarLegajoDocumento(
    empresaId: string,
    empleadoId: string,
    legajoDocumentoId: string | undefined,
    tipoJustificacion?: string,
  ) {
    if (!legajoDocumentoId) return;

    const doc = await this.prisma.rrhh_legajo_documentos.findFirst({
      where: {
        id: legajoDocumentoId,
        empresa_id: empresaId,
        empleado_id: empleadoId,
        estado: 'ACTIVO',
      },
      include: {
        tipo_documento: {
          select: { codigo: true },
        },
      },
    });
    if (!doc) {
      throw new BadRequestException(
        'legajo_documento_id inválido. El documento no pertenece al empleado/empresa o no está ACTIVO.',
      );
    }

    if (
      tipoJustificacion === 'MEDICA' &&
      doc.tipo_documento?.codigo !== 'CERTIFICADO_MED'
    ) {
      throw new BadRequestException(
        'Para justificación MEDICA, legajo_documento_id debe referenciar un documento CERTIFICADO_MED.',
      );
    }
  }

  private validarReglasNegocio(dto: CreatePermisoPresentismoDto) {
    if ((dto.hora_desde && !dto.hora_hasta) || (!dto.hora_desde && dto.hora_hasta)) {
      throw new BadRequestException('hora_desde y hora_hasta deben enviarse juntas');
    }

    if (dto.hora_desde && dto.hora_hasta) {
      const desde = parseHora(dto.hora_desde)!;
      const hasta = parseHora(dto.hora_hasta)!;
      if (desde >= hasta) {
        throw new BadRequestException('hora_hasta debe ser posterior a hora_desde');
      }
    }

    if (
      (dto.tipo_permiso === PermisoPresentismoTipo.TURNO ||
        dto.tipo_permiso === PermisoPresentismoTipo.AUSENCIA_TURNO) &&
      !dto.turno_bloque_orden
    ) {
      throw new BadRequestException('turno_bloque_orden es obligatorio para TURNO y AUSENCIA_TURNO');
    }

    if (dto.tipo_permiso === PermisoPresentismoTipo.LLEGADA_TARDIA && !dto.minutos_tolerados) {
      throw new BadRequestException('minutos_tolerados es obligatorio para LLEGADA_TARDIA');
    }

    if (dto.tipo_permiso === PermisoPresentismoTipo.JORNADA_COMPLETA) {
      if (dto.turno_bloque_orden || dto.hora_desde || dto.hora_hasta || dto.minutos_tolerados) {
        throw new BadRequestException(
          'JORNADA_COMPLETA no admite turno_bloque_orden, hora_desde, hora_hasta ni minutos_tolerados',
        );
      }
    }
  }

  private mapearPayload(dto: CreatePermisoPresentismoDto): {
    fecha: Date;
    tipo_permiso: Prisma.rrhh_permisos_presentismoUncheckedCreateInput['tipo_permiso'];
    turno_bloque_orden: number | null;
    hora_desde: Date | null;
    hora_hasta: Date | null;
    minutos_tolerados: number | null;
    legajo_documento_id: string | null;
    motivo: string;
    tipo_justificacion: Prisma.rrhh_permisos_presentismoUncheckedCreateInput['tipo_justificacion'];
    afecta_descuento: boolean;
  } {
    return {
      fecha: new Date(dto.fecha),
      tipo_permiso: dto.tipo_permiso as any,
      turno_bloque_orden: dto.turno_bloque_orden ?? null,
      hora_desde: parseHora(dto.hora_desde),
      hora_hasta: parseHora(dto.hora_hasta),
      minutos_tolerados: dto.minutos_tolerados ?? null,
      legajo_documento_id: dto.legajo_documento_id ?? null,
      motivo: dto.motivo,
      tipo_justificacion: dto.tipo_justificacion as any,
      afecta_descuento: dto.afecta_descuento ?? true,
    };
  }

  private serializar(
    row: Prisma.rrhh_permisos_presentismoGetPayload<{
      include: {
        empleado: {
          select: {
            id: true;
            numero_empleado: true;
            cedula_identidad: true;
            nombres: true;
            apellidos: true;
          };
        };
      };
    }>,
  ) {
    return {
      ...row,
      fecha: row.fecha,
      hora_desde: horaToString(row.hora_desde),
      hora_hasta: horaToString(row.hora_hasta),
    };
  }
}
