import { Injectable, Logger } from '@nestjs/common';
import { FacturasService } from 'src/facturas/facturas.service';
import { PrismaService } from 'src/prisma/prisma.service';

@Injectable()
export class SyncService {
  private readonly logger = new Logger(SyncService.name);

  constructor(
    private readonly prisma: PrismaService,
    private readonly facturasService: FacturasService,
  ) {}

  /**
   * Obtener productos modificados desde una fecha dada.
   * Incluye relaciones necesarias para el POS.
   */
  async getProductosDelta(empresaId: string, since?: string) {
    const where: any = {
      empresa_id: empresaId,
      deleted: false,
    };
    if (since) {
      where.updated_at = { gte: new Date(since) };
    }

    return this.prisma.productos.findMany({
      where,
      include: {
        unidades_de_medida: {
          select: { id: true, representacion: true, descripcion: true },
        },
        afectacion_iva: {
          select: { id: true, codigo: true, descripcion: true },
        },
        // rubro_id: el POS offline filtra el catálogo por rubro con la misma
        // regla que el backend (whereProductoPorRubro), y la herencia viene de acá.
        categoria: {
          select: { id: true, descripcion: true, color: true, icono: true, rubro_id: true },
        },
        marcas: { select: { id: true, descripcion: true } },
        stock_deposito: {
          select: {
            id: true,
            deposito_id: true,
            cantidad_disponible: true,
            cantidad_reservada: true,
          },
        },
      },
      orderBy: { updated_at: 'desc' },
    });
  }

  /**
   * Obtener clientes modificados desde una fecha dada.
   */
  async getClientesDelta(empresaId: string, since?: string) {
    const where: any = {
      deleted: false,
      personas: { empresa_id: empresaId },
    };

    // Filtrar clientes cuya empresa coincida a través de factura_cab o directamente
    // En realidad los clientes se relacionan con empresa vía persona
    const clientes = await this.prisma.clientes.findMany({
      where: {
        deleted: false,
        ...(since ? { updated_at: { gte: new Date(since) } } : {}),
        factura_cab: {
          some: { empresa_id: empresaId },
        },
      },
      include: {
        personas: {
          select: {
            id: true,
            razon_social: true,
            ruc: true,
            dv: true,
            nro_documento: true,
            direccion: true,
            telefono: true,
            celular: true,
            email: true,
            naturaleza_receptor: {
              select: { id: true, codigo: true, descripcion: true },
            },
            tipo_documento_identidad: {
              select: { id: true, codigo: true, descripcion: true },
            },
            tipo_contribuyente: {
              select: { id: true, codigo: true, descripcion: true },
            },
          },
        },
        tipo_operacion: {
          select: { id: true, codigo: true, descripcion: true },
        },
        lista_precios: { select: { id: true, nombre: true, codigo: true } },
      },
      orderBy: { updated_at: 'desc' },
    });

    // También incluir clientes que no tienen facturas pero pertenecen a la empresa
    // vía la persona (empresa_id en personas)
    const clientesPorPersona = await this.prisma.clientes.findMany({
      where: {
        deleted: false,
        ...(since ? { updated_at: { gte: new Date(since) } } : {}),
        personas: { empresa_id: empresaId },
        id: { notIn: clientes.map((c) => c.id) },
      },
      include: {
        personas: {
          select: {
            id: true,
            razon_social: true,
            ruc: true,
            dv: true,
            nro_documento: true,
            direccion: true,
            telefono: true,
            celular: true,
            email: true,
            naturaleza_receptor: {
              select: { id: true, codigo: true, descripcion: true },
            },
            tipo_documento_identidad: {
              select: { id: true, codigo: true, descripcion: true },
            },
            tipo_contribuyente: {
              select: { id: true, codigo: true, descripcion: true },
            },
          },
        },
        tipo_operacion: {
          select: { id: true, codigo: true, descripcion: true },
        },
        lista_precios: { select: { id: true, nombre: true, codigo: true } },
      },
      orderBy: { updated_at: 'desc' },
    });

    return [...clientes, ...clientesPorPersona];
  }

  /**
   * Obtener categorías de la empresa.
   */
  async getCategoriasDelta(empresaId: string, since?: string) {
    const where: any = {
      empresa_id: empresaId,
      deleted_at: false,
    };
    if (since) {
      where.updated_at = { gte: new Date(since) };
    }

    return this.prisma.categorias.findMany({
      where,
      select: {
        id: true,
        descripcion: true,
        activo: true,
        icono: true,
        color: true,
        padre_id: true,
        // Para que las pestañas de categorías del POS offline sigan el rubro.
        rubro_id: true,
        updated_at: true,
      },
      orderBy: { descripcion: 'asc' },
    });
  }

  /**
   * Obtener medios de pago activos para la empresa.
   */
  async getMediosPagoDelta(empresaId: string) {
    const empresaMedios = await this.prisma.empresa_medio_pago.findMany({
      where: { empresa_id: empresaId, activo: true },
      include: { medio_pago: true },
      orderBy: { orden: 'asc' },
    });

    return empresaMedios.map((em) => ({
      id: em.medio_pago.id,
      codigo: em.medio_pago.codigo,
      descripcion: em.medio_pago.descripcion,
      orden: em.orden,
    }));
  }

  /**
   * Obtener datos referenciales (no cambian frecuentemente).
   */
  async getReferenciales() {
    const [
      afectacion_iva,
      tipo_operacion,
      tipo_impuesto,
      tipo_transaccion,
      indicador_presencia,
      condicion_operacion,
      naturaleza_receptor,
      tipo_documento_identidad,
      tipo_contribuyente,
      forma_procesamiento_pago,
      monedas,
    ] = await Promise.all([
      this.prisma.afectacion_iva.findMany({ orderBy: { codigo: 'asc' } }),
      this.prisma.tipo_operacion.findMany({ orderBy: { codigo: 'asc' } }),
      this.prisma.tipo_impuesto.findMany({ orderBy: { codigo: 'asc' } }),
      this.prisma.tipo_transaccion.findMany({ orderBy: { codigo: 'asc' } }),
      this.prisma.indicador_presencia.findMany({ orderBy: { codigo: 'asc' } }),
      this.prisma.condicion_operacion.findMany({ orderBy: { codigo: 'asc' } }),
      this.prisma.naturaleza_receptor.findMany({ orderBy: { codigo: 'asc' } }),
      this.prisma.tipo_documento_identidad.findMany({
        orderBy: { codigo: 'asc' },
      }),
      this.prisma.tipo_contribuyente.findMany({ orderBy: { codigo: 'asc' } }),
      this.prisma.forma_procesamiento_pago.findMany({
        orderBy: { codigo: 'asc' },
      }),
      this.prisma.moneda.findMany({ orderBy: { codigo: 'asc' } }),
    ]);

    return {
      afectacion_iva,
      tipo_operacion,
      tipo_impuesto,
      tipo_transaccion,
      indicador_presencia,
      condicion_operacion,
      naturaleza_receptor,
      tipo_documento_identidad,
      tipo_contribuyente,
      forma_procesamiento_pago,
      monedas,
    };
  }

  /**
   * Obtener numeraciones activas de la empresa.
   */
  async getNumeraciones(empresaId: string) {
    return this.prisma.numeraciones_documento.findMany({
      where: {
        empresa_id: empresaId,
        active: true,
      },
      include: {
        punto_expedicion: {
          include: {
            empresas_sucursales: {
              select: {
                id: true,
                descripcion: true,
                punto_establecimiento: true,
              },
            },
          },
        },
        timbrado: true,
        tipo_documento: {
          select: { id: true, codigo: true, descripcion: true },
        },
      },
    });
  }

  /**
   * Obtener condiciones de pago de la empresa.
   */
  async getCondicionesPago(empresaId: string) {
    return this.prisma.condiciones_pago.findMany({
      where: {
        empresa_id: empresaId,
        activo: true,
      },
      orderBy: { descripcion: 'asc' },
    });
  }

  /**
   * Sync completo: devuelve todo lo necesario para el POS en una sola llamada.
   * Útil para la primera sincronización o sync manual completo.
   */
  async getFullSync(empresaId: string) {
    const [
      productos,
      clientes,
      categorias,
      mediosPago,
      referenciales,
      numeraciones,
      condicionesPago,
    ] = await Promise.all([
      this.getProductosDelta(empresaId),
      this.getClientesDelta(empresaId),
      this.getCategoriasDelta(empresaId),
      this.getMediosPagoDelta(empresaId),
      this.getReferenciales(),
      this.getNumeraciones(empresaId),
      this.getCondicionesPago(empresaId),
    ]);

    return {
      productos,
      clientes,
      categorias,
      medios_pago: mediosPago,
      referenciales,
      numeraciones,
      condiciones_pago: condicionesPago,
      synced_at: new Date().toISOString(),
    };
  }

  /**
   * Sync delta: solo lo que cambió desde la última sincronización.
   */
  async getDeltaSync(empresaId: string, since: string) {
    const [productos, clientes, categorias] = await Promise.all([
      this.getProductosDelta(empresaId, since),
      this.getClientesDelta(empresaId, since),
      this.getCategoriasDelta(empresaId, since),
    ]);

    // Medios de pago, referenciales, numeraciones cambian poco,
    // pero los incluimos para mantener consistencia
    const [mediosPago, numeraciones, condicionesPago] = await Promise.all([
      this.getMediosPagoDelta(empresaId),
      this.getNumeraciones(empresaId),
      this.getCondicionesPago(empresaId),
    ]);

    return {
      productos,
      clientes,
      categorias,
      medios_pago: mediosPago,
      numeraciones,
      condiciones_pago: condicionesPago,
      synced_at: new Date().toISOString(),
      is_delta: true,
      since,
    };
  }

  /**
   * Recibir ventas realizadas offline.
   * Procesa cada venta usando FacturasService.create() para aplicar
   * las mismas validaciones y lógica que una venta online
   * (numeración atómica, inventario, pagos, subtotales, SIFEN, etc.).
   */
  async pushVentasOffline(empresaId: string, ventas: any[]) {
    const results = [];

    for (const venta of ventas) {
      const offlineId = venta._offline_id || 'sin-id';

      try {
        this.logger.log(
          `Procesando venta offline: ${offlineId} para empresa ${empresaId}`,
        );

        // Extraer los campos que espera CreateFacturaDto
        // (cabecera, items, pagos, cuotas, subtotal)
        // eslint-disable-next-line @typescript-eslint/no-unused-vars
        const { _offline_id, _offline_created_at, ...facturaData } = venta;

        const resultado = await this.facturasService.create(
          facturaData,
          empresaId,
        );

        this.logger.log(
          `Venta offline ${offlineId} procesada exitosamente: ${resultado.data?.id}`,
        );

        results.push({
          _offline_id: offlineId,
          status: 'success',
          message: resultado.message,
          factura_id: resultado.data?.id,
        });
      } catch (error) {
        const errorMessage = error?.message || String(error);
        this.logger.error(
          `Error procesando venta offline ${offlineId}: ${errorMessage}`,
        );
        results.push({
          _offline_id: offlineId,
          status: 'error',
          message: errorMessage,
        });
      }
    }

    return {
      processed: results.length,
      success: results.filter((r) => r.status === 'success').length,
      errors: results.filter((r) => r.status === 'error').length,
      results,
    };
  }
}
