import { Injectable } from "@nestjs/common";
import { Repository } from "typeorm";
import { OrdenTrabajo } from "../ots/entities/orden-trabajo.entity";
import { TenantRepositoryAccessor } from "../tenancy/tenant-repository.accessor";

// Los reportes se calculan en la base del tenant, NO en el navegador.
// Antes esta pantalla sumaba sobre el cache local de IndexedDB, que guarda
// las OTs pero no siempre su cotizacion: el resultado era una pantalla que
// mostraba Q 0.00 en todos los montos aunque hubiera cotizaciones reales
// aprobadas. Un reporte que miente es peor que no tener reporte, sobre todo
// si se le muestra a un dueño de taller.
//
// Todo con SUM/COUNT/GROUP BY del motor (no trayendo filas para sumarlas en
// JS): son tablas que crecen con cada recepcion, y este modulo tiene que
// seguir respondiendo igual con 50 OTs que con 50.000.

export type ResumenReportes = {
  totalOts: number;
  totalClientes: number;
  totalVehiculos: number;
  totalCotizado: number;
  totalCotizadoAprobado: number;
  ticketPromedio: number;
  cotizacionesPendientes: number;
  porEstado: Record<string, number>;
  topClientes: Array<{
    tarjeta: string;
    nombre: string | null;
    recepciones: number;
    totalCotizado: number;
  }>;
  topVehiculos: Array<{
    placa: string;
    marca: string | null;
    modelo: string | null;
    visitas: number;
  }>;
  recientes: Array<{
    numeroOt: string | null;
    estado: string;
    fechaEntrada: string | null;
    placa: string | null;
    clienteTarjeta: string | null;
    clienteNombre: string | null;
    total: number;
  }>;
};

function toNumber(value: unknown): number {
  const parsed = Number(value ?? 0);
  return Number.isFinite(parsed) ? parsed : 0;
}

@Injectable()
export class ReportesService {
  constructor(private readonly tenantAccessor: TenantRepositoryAccessor) {}

  private get otRepository(): Repository<OrdenTrabajo> {
    return this.tenantAccessor.repositoryFor(OrdenTrabajo);
  }

  async getResumen(): Promise<ResumenReportes> {
    const repo = this.otRepository;

    const [
      totalesRows,
      estadoRows,
      cotizacionRows,
      topClientesRows,
      topVehiculosRows,
      recientesRows,
    ] = await Promise.all([
      repo.query(
        `SELECT COUNT(*) AS totalOts,
                COUNT(DISTINCT cliente_tarjeta) AS totalClientes,
                COUNT(DISTINCT vehiculo_id) AS totalVehiculos
           FROM taller_ordenes_trabajo`,
      ),
      repo.query(
        `SELECT estado, COUNT(*) AS total
           FROM taller_ordenes_trabajo
          GROUP BY estado`,
      ),
      // "Pendiente" = cualquier cotizacion que todavia no esta aprobada
      // (borrador, enviada o rechazada) — mismo criterio que ya usaba la
      // pantalla, ahora contado por el motor.
      repo.query(
        `SELECT COALESCE(SUM(total), 0) AS totalCotizado,
                COALESCE(SUM(CASE WHEN estado = 'aprobada' THEN total ELSE 0 END), 0) AS totalAprobado,
                COALESCE(SUM(CASE WHEN estado <> 'aprobada' THEN 1 ELSE 0 END), 0) AS pendientes,
                COALESCE(AVG(CASE WHEN total > 0 THEN total END), 0) AS ticketPromedio
           FROM taller_cotizaciones`,
      ),
      // COUNT(DISTINCT ot.id) y no COUNT(*): el LEFT JOIN a cotizaciones
      // multiplica la fila de la OT por cada cotizacion que tenga, y sin el
      // DISTINCT una OT cotizada dos veces contaria como dos recepciones.
      repo.query(
        `SELECT ot.cliente_tarjeta AS tarjeta,
                MAX(cli.nombre) AS nombre,
                COUNT(DISTINCT ot.id) AS recepciones,
                COALESCE(SUM(c.total), 0) AS totalCotizado
           FROM taller_ordenes_trabajo ot
           LEFT JOIN taller_cotizaciones c ON c.ot_id = ot.id
           LEFT JOIN maecli cli ON cli.tarjeta = ot.cliente_tarjeta
          WHERE ot.cliente_tarjeta IS NOT NULL AND ot.cliente_tarjeta <> ''
          GROUP BY ot.cliente_tarjeta
          ORDER BY recepciones DESC, totalCotizado DESC
          LIMIT 5`,
      ),
      repo.query(
        `SELECT v.placa AS placa,
                MAX(v.marca) AS marca,
                MAX(v.modelo) AS modelo,
                COUNT(*) AS visitas
           FROM taller_ordenes_trabajo ot
           INNER JOIN taller_vehiculos v ON v.id = ot.vehiculo_id
          WHERE v.placa IS NOT NULL AND v.placa <> ''
          GROUP BY v.placa
          ORDER BY visitas DESC
          LIMIT 5`,
      ),
      // Se toma la ULTIMA cotizacion de cada OT (id mas alto), no la suma:
      // en el listado de recepciones recientes lo que interesa es el monto
      // vigente de esa OT, no el acumulado de sus versiones.
      repo.query(
        `SELECT ot.numero_ot AS numeroOt,
                ot.estado AS estado,
                ot.fecha_entrada AS fechaEntrada,
                v.placa AS placa,
                ot.cliente_tarjeta AS clienteTarjeta,
                cli.nombre AS clienteNombre,
                COALESCE((SELECT c.total
                            FROM taller_cotizaciones c
                           WHERE c.ot_id = ot.id
                           ORDER BY c.id DESC
                           LIMIT 1), 0) AS total
           FROM taller_ordenes_trabajo ot
           LEFT JOIN taller_vehiculos v ON v.id = ot.vehiculo_id
           LEFT JOIN maecli cli ON cli.tarjeta = ot.cliente_tarjeta
          ORDER BY ot.fecha_entrada DESC
          LIMIT 20`,
      ),
    ]);

    const totales = totalesRows?.[0] || {};
    const cotizaciones = cotizacionRows?.[0] || {};

    const porEstado: Record<string, number> = {};
    for (const row of estadoRows || []) {
      porEstado[String(row.estado)] = toNumber(row.total);
    }

    return {
      totalOts: toNumber(totales.totalOts),
      totalClientes: toNumber(totales.totalClientes),
      totalVehiculos: toNumber(totales.totalVehiculos),
      totalCotizado: toNumber(cotizaciones.totalCotizado),
      totalCotizadoAprobado: toNumber(cotizaciones.totalAprobado),
      ticketPromedio: toNumber(cotizaciones.ticketPromedio),
      cotizacionesPendientes: toNumber(cotizaciones.pendientes),
      porEstado,
      topClientes: (topClientesRows || []).map((row: any) => ({
        tarjeta: String(row.tarjeta),
        nombre: row.nombre ? String(row.nombre).trim() : null,
        recepciones: toNumber(row.recepciones),
        totalCotizado: toNumber(row.totalCotizado),
      })),
      topVehiculos: (topVehiculosRows || []).map((row: any) => ({
        placa: String(row.placa),
        marca: row.marca ? String(row.marca).trim() : null,
        modelo: row.modelo ? String(row.modelo).trim() : null,
        visitas: toNumber(row.visitas),
      })),
      recientes: (recientesRows || []).map((row: any) => ({
        numeroOt: row.numeroOt ? String(row.numeroOt) : null,
        estado: String(row.estado),
        fechaEntrada: row.fechaEntrada
          ? new Date(row.fechaEntrada).toISOString()
          : null,
        placa: row.placa ? String(row.placa) : null,
        clienteTarjeta: row.clienteTarjeta ? String(row.clienteTarjeta) : null,
        clienteNombre: row.clienteNombre ? String(row.clienteNombre).trim() : null,
        total: toNumber(row.total),
      })),
    };
  }
}
