import { Injectable } from '@nestjs/common';
import { Prisma } from '@prisma/client';
import { PrismaService } from 'src/prisma/prisma.service';
import { alcanceSucursalUsuario, filtroFiscalPorSucursal } from 'src/utils/alcance-sucursal';

export type AgruparPor = 'chofer' | 'transportista' | 'vehiculo' | 'cliente' | 'destino' | 'motivo' | 'estado';
export type EstadoFacturacion = 'sin_facturar' | 'parcial' | 'facturada';

export interface FiltrosReporteRemision {
  fecha_desde?: string; // YYYY-MM-DD
  fecha_hasta?: string; // YYYY-MM-DD
  estado?: string;
  estado_sifen?: string;
  cliente_id?: string;
  transportista_id?: string;
  chofer_id?: string;
  vehiculo_id?: string;
  motivo_id?: string;
  facturacion?: EstadoFacturacion;
}

// Selección liviana para el reporte (etiquetas de las dimensiones + métricas del detalle).
const remisionSelect = {
  id: true,
  dest: true,
  dpunexp: true,
  dnumdoc: true,
  dfeemide: true,
  dkmr: true,
  estado: true,
  estado_sifen: true,
  chofer_id: true,
  transportista_id: true,
  vehiculo_id: true,
  cliente_id: true,
  motivo_emision_id: true,
  ciudad_entrega_id: true,
  choferes: { select: { personas: { select: { razon_social: true } } } },
  transportistas: { select: { personas: { select: { razon_social: true } } } },
  vehiculos: { select: { marca: true, modelo: true, nro_matricula: true, nro_identificacion: true } },
  clientes: { select: { nombre_fantasia: true, personas: { select: { razon_social: true } } } },
  motivo_emision_remision: { select: { descripcion: true } },
  ciudades_nota_remision_cab_ciudad_salida_idTociudades: {
    select: { descripcion: true, distritos: { select: { departamentos: { select: { descripcion: true } } } } },
  },
  ciudades_nota_remision_cab_ciudad_entrega_idTociudades: {
    select: { descripcion: true, distritos: { select: { departamentos: { select: { descripcion: true } } } } },
  },
  // La remisión no guarda moneda, por eso el reporte no expone valor monetario
  // (evita sumar montos de distintas monedas). Solo métricas operativas.
  nota_remision_det: { select: { dcantproser: true, cantidad_facturada: true } },
} satisfies Prisma.nota_remision_cabSelect;

type RemisionRow = Prisma.nota_remision_cabGetPayload<{ select: typeof remisionSelect }>;

// Métricas calculadas por remisión (operativas, sin monto — la remisión no tiene moneda).
interface RemisionMetrica {
  unidades: number;
  totalCant: number;
  factCant: number;
  estadoFact: EstadoFacturacion;
}

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

  private rangoFechas(desde?: string, hasta?: string): { gte: Date; lte: Date } {
    const hoy = new Date();
    const primerDia = `${hoy.getFullYear()}-${String(hoy.getMonth() + 1).padStart(2, '0')}-01`;
    const ultimoDiaDate = new Date(hoy.getFullYear(), hoy.getMonth() + 1, 0);
    const ultimoDia = `${ultimoDiaDate.getFullYear()}-${String(ultimoDiaDate.getMonth() + 1).padStart(2, '0')}-${String(ultimoDiaDate.getDate()).padStart(2, '0')}`;
    const d = desde || primerDia;
    const h = hasta || ultimoDia;
    return { gte: new Date(`${d}T00:00:00`), lte: new Date(`${h}T23:59:59.999`) };
  }

  /**
   * `filtroSucursal` ya resuelto a `{ dest: { in: [...] } }`. La remisión no
   * tiene `sucursal_id`: se ata por `dest`, el punto de establecimiento.
   */
  private buildWhere(
    empresaId: string,
    f: FiltrosReporteRemision,
    filtroSucursal?: { dest: { in: string[] } } | null,
  ): Prisma.nota_remision_cabWhereInput {
    const { gte, lte } = this.rangoFechas(f.fecha_desde, f.fecha_hasta);
    const where: Prisma.nota_remision_cabWhereInput = {
      empresa_id: empresaId,
      dfeemide: { gte, lte },
      ...(filtroSucursal ?? {}),
    };
    if (f.estado) where.estado = f.estado;
    if (f.estado_sifen) where.estado_sifen = f.estado_sifen;
    if (f.cliente_id) where.cliente_id = f.cliente_id;
    if (f.transportista_id) where.transportista_id = f.transportista_id;
    if (f.chofer_id) where.chofer_id = f.chofer_id;
    if (f.vehiculo_id) where.vehiculo_id = f.vehiculo_id;
    if (f.motivo_id) where.motivo_emision_id = f.motivo_id;
    return where;
  }

  private metrica(rem: RemisionRow): RemisionMetrica {
    let unidades = 0;
    let factCant = 0;
    for (const d of rem.nota_remision_det) {
      unidades += Number(d.dcantproser) || 0;
      factCant += Number(d.cantidad_facturada) || 0;
    }
    const totalCant = unidades;
    const estadoFact: EstadoFacturacion =
      factCant <= 0 ? 'sin_facturar' : factCant >= totalCant && totalCant > 0 ? 'facturada' : 'parcial';
    return { unidades, totalCant, factCant, estadoFact };
  }

  private vehiculoLabel(v: RemisionRow['vehiculos']): string {
    if (!v) return 'Sin vehículo';
    const ident = v.nro_matricula || v.nro_identificacion || '';
    const base = [v.marca, v.modelo].filter(Boolean).join(' ');
    return [base, ident ? `(${ident})` : ''].filter(Boolean).join(' ') || 'Sin vehículo';
  }

  private ciudadLabel(c: RemisionRow['ciudades_nota_remision_cab_ciudad_entrega_idTociudades']): string {
    if (!c) return 'Sin destino';
    const dep = c.distritos?.departamentos?.descripcion;
    return dep ? `${c.descripcion} (${dep})` : c.descripcion || 'Sin destino';
  }

  // Clave + etiqueta de la dimensión de agrupación para una remisión.
  private dimension(rem: RemisionRow, agruparPor: AgruparPor): { key: string; label: string } {
    switch (agruparPor) {
      case 'chofer':
        return { key: rem.chofer_id || 'sin', label: rem.choferes?.personas?.razon_social || 'Sin chofer' };
      case 'transportista':
        return {
          key: rem.transportista_id || 'sin',
          label: rem.transportistas?.personas?.razon_social || 'Sin transportista',
        };
      case 'vehiculo':
        return { key: rem.vehiculo_id || 'sin', label: this.vehiculoLabel(rem.vehiculos) };
      case 'cliente':
        return {
          key: rem.cliente_id || 'sin',
          label: rem.clientes?.personas?.razon_social || rem.clientes?.nombre_fantasia || 'Sin cliente',
        };
      case 'destino':
        return {
          key: rem.ciudad_entrega_id || 'sin',
          label: this.ciudadLabel(rem.ciudades_nota_remision_cab_ciudad_entrega_idTociudades),
        };
      case 'motivo':
        return { key: rem.motivo_emision_id || 'sin', label: rem.motivo_emision_remision?.descripcion || 'Sin motivo' };
      case 'estado':
      default:
        return { key: rem.estado || 'sin', label: rem.estado || 'Sin estado' };
    }
  }

  /**
   * Resumen agregado por la dimensión elegida + KPIs globales del período.
   */
  async getResumen(empresaId: string, agruparPor: AgruparPor, f: FiltrosReporteRemision, usuarioId?: string) {
    const remisiones = await this.prisma.nota_remision_cab.findMany({
      where: this.buildWhere(empresaId, f, await filtroFiscalPorSucursal(this.prisma, await alcanceSucursalUsuario(this.prisma, usuarioId, empresaId))),
      select: remisionSelect,
      take: 20000,
    });

    // KPIs (operativos, sin monto)
    let kpiRemisiones = 0;
    let kpiKm = 0;
    let kpiUnidades = 0;
    let kpiFacturadas = 0;
    let kpiParciales = 0;
    let kpiSinFacturar = 0;
    let kpiAnuladas = 0;

    // Acumulador por grupo
    const grupos = new Map<
      string,
      { key: string; label: string; remisiones: number; km: number; unidades: number; facturadas: number; pendientes: number }
    >();

    for (const rem of remisiones) {
      const m = this.metrica(rem);
      if (f.facturacion && m.estadoFact !== f.facturacion) continue;

      const km = Number(rem.dkmr) || 0;
      kpiRemisiones += 1;
      kpiKm += km;
      kpiUnidades += m.unidades;
      if (rem.estado === 'Anulado') kpiAnuladas += 1;
      if (m.estadoFact === 'facturada') kpiFacturadas += 1;
      else if (m.estadoFact === 'parcial') kpiParciales += 1;
      else kpiSinFacturar += 1;

      const dim = this.dimension(rem, agruparPor);
      const g = grupos.get(dim.key) || {
        key: dim.key,
        label: dim.label,
        remisiones: 0,
        km: 0,
        unidades: 0,
        facturadas: 0,
        pendientes: 0,
      };
      g.remisiones += 1;
      g.km += km;
      g.unidades += m.unidades;
      if (m.estadoFact === 'facturada') g.facturadas += 1;
      else g.pendientes += 1;
      grupos.set(dim.key, g);
    }

    // % del total = participación por unidades trasladadas (métrica sin moneda).
    const gruposArr = Array.from(grupos.values())
      .map((g) => ({
        ...g,
        km: Math.round(g.km),
        km_prom: g.remisiones ? Math.round(g.km / g.remisiones) : 0,
        unidades: Math.round(g.unidades * 10000) / 10000,
        pct: kpiUnidades ? Math.round((g.unidades / kpiUnidades) * 1000) / 10 : 0,
      }))
      .sort((a, b) => b.remisiones - a.remisiones || b.unidades - a.unidades);

    return {
      agrupar_por: agruparPor,
      kpis: {
        remisiones: kpiRemisiones,
        km_total: Math.round(kpiKm),
        unidades: Math.round(kpiUnidades * 10000) / 10000,
        facturadas: kpiFacturadas,
        parciales: kpiParciales,
        sin_facturar: kpiSinFacturar,
        anuladas: kpiAnuladas,
        pct_facturadas: kpiRemisiones ? Math.round((kpiFacturadas / kpiRemisiones) * 1000) / 10 : 0,
      },
      grupos: gruposArr,
    };
  }

  /**
   * Detalle plano: una fila por remisión (paginado). Aplica el filtro de
   * facturación en memoria para ser consistente con el resumen.
   */
  async getDetalle(empresaId: string, f: FiltrosReporteRemision, page = 1, pageSize = 20, usuarioId?: string) {
    const remisiones = await this.prisma.nota_remision_cab.findMany({
      where: this.buildWhere(empresaId, f, await filtroFiscalPorSucursal(this.prisma, await alcanceSucursalUsuario(this.prisma, usuarioId, empresaId))),
      select: remisionSelect,
      orderBy: { dfeemide: 'desc' },
      take: 20000,
    });

    const filas = remisiones
      .map((rem) => {
        const m = this.metrica(rem);
        return { rem, m };
      })
      .filter(({ m }) => !f.facturacion || m.estadoFact === f.facturacion)
      .map(({ rem, m }) => ({
        id: rem.id,
        numero: `${rem.dest ?? ''}-${rem.dpunexp ?? ''}-${rem.dnumdoc ?? ''}`,
        fecha: rem.dfeemide,
        cliente: rem.clientes?.personas?.razon_social || rem.clientes?.nombre_fantasia || null,
        transportista: rem.transportistas?.personas?.razon_social || null,
        chofer: rem.choferes?.personas?.razon_social || null,
        vehiculo: this.vehiculoLabel(rem.vehiculos),
        motivo: rem.motivo_emision_remision?.descripcion || null,
        origen: this.ciudadLabel(rem.ciudades_nota_remision_cab_ciudad_salida_idTociudades),
        destino: this.ciudadLabel(rem.ciudades_nota_remision_cab_ciudad_entrega_idTociudades),
        km: Number(rem.dkmr) || 0,
        unidades: Math.round(m.unidades * 10000) / 10000,
        estado: rem.estado,
        estado_sifen: rem.estado_sifen,
        facturacion: m.estadoFact,
        facturado_pct: m.totalCant ? Math.round((m.factCant / m.totalCant) * 1000) / 10 : 0,
      }));

    const total = filas.length;
    const start = (page - 1) * pageSize;
    const data = filas.slice(start, start + pageSize);
    return { data, total, page, pageSize, lastPage: Math.max(1, Math.ceil(total / pageSize)) };
  }

  /**
   * Remisiones pendientes de facturar (Fase 2): aprobadas, SIN documento asociado,
   * con saldo por facturar. Agrupadas por cliente, con antigüedad y valor pendiente.
   */
  async getPendientesFacturar(
    empresaId: string,
    opts: { fecha_desde?: string; fecha_hasta?: string; cliente_id?: string; dias_min?: number } = {},
  ) {
    const where: Prisma.nota_remision_cabWhereInput = {
      empresa_id: empresaId,
      estado: 'Aprobado',
      tipo_documento_asociado: null,
    };
    if (opts.cliente_id) where.cliente_id = opts.cliente_id;
    if (opts.fecha_desde || opts.fecha_hasta) {
      const { gte, lte } = this.rangoFechas(opts.fecha_desde, opts.fecha_hasta);
      where.dfeemide = { gte, lte };
    }

    const remisiones = await this.prisma.nota_remision_cab.findMany({
      where,
      select: remisionSelect,
      orderBy: { dfeemide: 'asc' },
      take: 20000,
    });

    const ahora = Date.now();
    const diasMin = Number(opts.dias_min) || 0;

    // Acumulador por cliente (sin monto — la remisión no tiene moneda).
    const clientes = new Map<
      string,
      {
        cliente_id: string;
        cliente: string;
        remisiones: Array<Record<string, unknown>>;
        unidades_pendientes: number;
      }
    >();

    let kpiUnidades = 0;
    let kpiRemisiones = 0;
    let sumDias = 0;

    for (const rem of remisiones) {
      let pendUnidades = 0;
      for (const d of rem.nota_remision_det) {
        const pend = (Number(d.dcantproser) || 0) - (Number(d.cantidad_facturada) || 0);
        if (pend > 0) pendUnidades += pend;
      }
      if (pendUnidades <= 0) continue;

      const dias = rem.dfeemide ? Math.floor((ahora - new Date(rem.dfeemide).getTime()) / 86400000) : 0;
      if (dias < diasMin) continue;

      const cid = rem.cliente_id || 'sin';
      const g = clientes.get(cid) || {
        cliente_id: cid,
        cliente: rem.clientes?.personas?.razon_social || rem.clientes?.nombre_fantasia || 'Sin cliente',
        remisiones: [],
        unidades_pendientes: 0,
      };
      g.remisiones.push({
        id: rem.id,
        numero: `${rem.dest ?? ''}-${rem.dpunexp ?? ''}-${rem.dnumdoc ?? ''}`,
        fecha: rem.dfeemide,
        dias_sin_facturar: dias,
        unidades_pendientes: Math.round(pendUnidades * 10000) / 10000,
        chofer: rem.choferes?.personas?.razon_social || null,
        vehiculo: this.vehiculoLabel(rem.vehiculos),
      });
      g.unidades_pendientes += pendUnidades;
      clientes.set(cid, g);

      kpiRemisiones += 1;
      kpiUnidades += pendUnidades;
      sumDias += dias;
    }

    const clientesArr = Array.from(clientes.values())
      .map((c) => ({
        ...c,
        unidades_pendientes: Math.round(c.unidades_pendientes * 10000) / 10000,
        cantidad_remisiones: c.remisiones.length,
      }))
      .sort((a, b) => b.unidades_pendientes - a.unidades_pendientes);

    return {
      kpis: {
        unidades_pendientes: Math.round(kpiUnidades * 10000) / 10000,
        remisiones: kpiRemisiones,
        clientes: clientesArr.length,
        antiguedad_promedio: kpiRemisiones ? Math.round(sumDias / kpiRemisiones) : 0,
      },
      clientes: clientesArr,
    };
  }
}
