import { Injectable } from '@nestjs/common';
import { Prisma } from '@prisma/client';
import * as XLSX from 'xlsx';
import { PrismaService } from '../../prisma/prisma.service';
import { ExportarLegajosConsultaDto, QueryLegajosConsultaDto } from '../dto/legajos.dto';

interface ConsultaCursor {
  subido_en: string;
  id: string;
}

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

  async consultar(empresaId: string, query: QueryLegajosConsultaDto) {
    const limit = Math.max(1, Math.min(100, Number(query.limit || 50)));
    const decodedCursor = this.decodeCursor(query.cursor);

    const where = this.buildWhere(empresaId, query, decodedCursor);

    const rows = await this.prisma.rrhh_legajo_documentos.findMany({
      where,
      take: limit + 1,
      orderBy: [{ subido_en: 'desc' }, { id: 'desc' }],
      include: {
        tipo_documento: { select: { id: true, codigo: true, nombre: true } },
        empleado: {
          select: {
            id: true,
            numero_empleado: true,
            nombres: true,
            apellidos: true,
            sucursal_id: true,
            departamento_id: true,
            sucursal: { select: { descripcion: true } },
            departamento: { select: { nombre: true } },
          },
        },
      },
    });

    const hasMore = rows.length > limit;
    const items = hasMore ? rows.slice(0, limit) : rows;

    const nextCursor = hasMore
      ? this.encodeCursor({
          subido_en: (items[items.length - 1].subido_en as Date).toISOString(),
          id: items[items.length - 1].id,
        })
      : null;

    return {
      data: items.map((r) => this.serialize(r)),
      limit,
      cursor_next: nextCursor,
      has_more: hasMore,
    };
  }

  async exportarExcel(empresaId: string, query: ExportarLegajosConsultaDto) {
    const where = this.buildWhere(empresaId, query, null);

    const rows = await this.prisma.rrhh_legajo_documentos.findMany({
      where,
      take: 5000,
      orderBy: [{ subido_en: 'desc' }, { id: 'desc' }],
      include: {
        tipo_documento: { select: { codigo: true, nombre: true } },
        empleado: {
          select: {
            numero_empleado: true,
            nombres: true,
            apellidos: true,
            sucursal: { select: { descripcion: true } },
            departamento: { select: { nombre: true } },
          },
        },
      },
    });

    const header = [
      'N° Empleado',
      'Empleado',
      'Sucursal',
      'Departamento',
      'Tipo código',
      'Tipo nombre',
      'Documento',
      'Estado documento',
      'Estado vencimiento',
      'Fecha documento',
      'Fecha vencimiento',
      'Subido en',
      'Tamaño bytes',
      'MIME',
      'Extensión',
      'Versión',
    ];

    const body = rows.map((r) => [
      r.empleado.numero_empleado || '',
      `${r.empleado.apellidos || ''} ${r.empleado.nombres || ''}`.trim(),
      r.empleado.sucursal?.descripcion || '',
      r.empleado.departamento?.nombre || '',
      r.tipo_documento?.codigo || '',
      r.tipo_documento?.nombre || '',
      r.nombre_original,
      r.estado,
      r.estado_vencimiento,
      this.dateOnly(r.fecha_documento),
      this.dateOnly(r.fecha_vencimiento),
      this.dateTime(r.subido_en),
      Number(r.tamanio_bytes || 0),
      r.mime_type,
      r.extension,
      r.version,
    ]);

    const ws = XLSX.utils.aoa_to_sheet([header, ...body]);
    ws['!cols'] = [
      { wch: 14 },
      { wch: 28 },
      { wch: 16 },
      { wch: 16 },
      { wch: 14 },
      { wch: 26 },
      { wch: 36 },
      { wch: 14 },
      { wch: 16 },
      { wch: 14 },
      { wch: 14 },
      { wch: 20 },
      { wch: 14 },
      { wch: 24 },
      { wch: 10 },
      { wch: 8 },
    ];

    const wb = XLSX.utils.book_new();
    XLSX.utils.book_append_sheet(wb, ws, 'Consulta legajos');

    const buffer = XLSX.write(wb, { type: 'buffer', bookType: 'xlsx' }) as Buffer;
    const filename = `legajos-consulta-${new Date().toISOString().slice(0, 10)}.xlsx`;

    return { buffer, filename, total_rows: rows.length };
  }

  private buildWhere(
    empresaId: string,
    query: QueryLegajosConsultaDto,
    cursor: ConsultaCursor | null,
  ): Prisma.rrhh_legajo_documentosWhereInput {
    const where: Prisma.rrhh_legajo_documentosWhereInput = {
      empresa_id: empresaId,
      estado: (query.estado_documento || 'ACTIVO') as any,
    };

    if (query.empleado_id) where.empleado_id = query.empleado_id;
    if (query.tipo_documento_id) where.tipo_documento_id = query.tipo_documento_id;
    if (query.estado_vencimiento) where.estado_vencimiento = query.estado_vencimiento as any;

    if (query.vence_desde || query.vence_hasta) {
      where.fecha_vencimiento = {
        ...(query.vence_desde ? { gte: new Date(`${query.vence_desde}T00:00:00.000Z`) } : {}),
        ...(query.vence_hasta ? { lte: new Date(`${query.vence_hasta}T23:59:59.999Z`) } : {}),
      };
    }

    if (query.subido_desde || query.subido_hasta) {
      where.subido_en = {
        ...(query.subido_desde ? { gte: new Date(`${query.subido_desde}T00:00:00.000Z`) } : {}),
        ...(query.subido_hasta ? { lte: new Date(`${query.subido_hasta}T23:59:59.999Z`) } : {}),
      };
    }

    if (query.sucursal_id || query.departamento_id) {
      where.empleado = {
        ...(query.sucursal_id ? { sucursal_id: query.sucursal_id } : {}),
        ...(query.departamento_id ? { departamento_id: query.departamento_id } : {}),
      };
    }

    if (query.q) {
      const q = query.q.trim();
      where.OR = [
        { nombre_original: { contains: q, mode: 'insensitive' } },
        { tipo_documento: { codigo: { contains: q, mode: 'insensitive' } } },
        { tipo_documento: { nombre: { contains: q, mode: 'insensitive' } } },
        { empleado: { numero_empleado: { contains: q, mode: 'insensitive' } } },
        { empleado: { nombres: { contains: q, mode: 'insensitive' } } },
        { empleado: { apellidos: { contains: q, mode: 'insensitive' } } },
      ];
    }

    if (cursor) {
      const cursorDate = new Date(cursor.subido_en);
      if (!Number.isNaN(cursorDate.getTime()) && cursor.id) {
        // Prisma tipa AND como objeto | array; normalizamos a array antes de extender.
        const andPrevias = Array.isArray(where.AND) ? where.AND : where.AND ? [where.AND] : [];
        where.AND = [
          ...andPrevias,
          {
            OR: [
              { subido_en: { lt: cursorDate } },
              {
                AND: [
                  { subido_en: { equals: cursorDate } },
                  { id: { lt: cursor.id } },
                ],
              },
            ],
          },
        ];
      }
    }

    return where;
  }

  private decodeCursor(cursorRaw?: string): ConsultaCursor | null {
    if (!cursorRaw) return null;
    try {
      const parsed = JSON.parse(Buffer.from(cursorRaw, 'base64').toString('utf8')) as ConsultaCursor;
      if (!parsed?.subido_en || !parsed?.id) return null;
      return parsed;
    } catch {
      return null;
    }
  }

  private encodeCursor(cursor: ConsultaCursor): string {
    return Buffer.from(JSON.stringify(cursor), 'utf8').toString('base64');
  }

  private serialize(row: any) {
    return {
      ...row,
      tamanio_bytes:
        typeof row.tamanio_bytes === 'bigint' ? row.tamanio_bytes.toString() : row.tamanio_bytes,
    };
  }

  private dateOnly(value: Date | null | undefined): string {
    if (!value) return '';
    return new Date(value).toISOString().slice(0, 10);
  }

  private dateTime(value: Date | null | undefined): string {
    if (!value) return '';
    return new Date(value).toISOString().slice(0, 19).replace('T', ' ');
  }
}
