import { Injectable, Logger } from "@nestjs/common";
import { InjectRepository } from "@nestjs/typeorm";
import { MoreThan, Repository } from "typeorm";
import { CosaTrae } from "../catalogos/entities/cosa-trae.entity";
import { TipoDano } from "../catalogos/entities/tipo-dano.entity";
import { TipoVehiculo } from "../catalogos/entities/tipo-vehiculo.entity";
import { CatalogosService } from "../catalogos/catalogos.service";
import { ClienteLocal } from "../clientes/entities/cliente-local.entity";
import { ClientesService } from "../clientes/clientes.service";
import { OrdenTrabajo } from "../ots/entities/orden-trabajo.entity";
import { OtsService } from "../ots/ots.service";
import { Vehiculo } from "../vehiculos/entities/vehiculo.entity";
import { VehiculosService } from "../vehiculos/vehiculos.service";

export interface SyncEvent {
  eventId: string;
  eventType: string;
  entityType: string;
  localId: string;
  payload: any;
  timestamp: number;
}

export interface SyncResult {
  success: boolean;
  processed: number;
  failed: number;
  errors: any[];
  mappings: Record<string, any>;
}

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

  constructor(
    private clientesService: ClientesService,
    private vehiculosService: VehiculosService,
    private otsService: OtsService,
    private catalogosService: CatalogosService,
    @InjectRepository(OrdenTrabajo)
    private otRepository: Repository<OrdenTrabajo>,
    @InjectRepository(Vehiculo)
    private vehiculoRepository: Repository<Vehiculo>,
    @InjectRepository(ClienteLocal)
    private clienteLocalRepository: Repository<ClienteLocal>,
    @InjectRepository(TipoVehiculo)
    private tipoVehiculoRepository: Repository<TipoVehiculo>,
    @InjectRepository(TipoDano)
    private tipoDanoRepository: Repository<TipoDano>,
    @InjectRepository(CosaTrae)
    private cosaTraeRepository: Repository<CosaTrae>,
  ) {}

  async processPushEvents(
    events: SyncEvent[],
    userId?: number,
  ): Promise<SyncResult> {
    this.logger.log(`Processing ${events.length} sync events`);

    const result: SyncResult = {
      success: true,
      processed: 0,
      failed: 0,
      errors: [],
      mappings: {},
    };

    const localMappings = new Map<string, any>();
    const sortedEvents = [...events].sort((a, b) => a.timestamp - b.timestamp);

    for (const event of sortedEvents) {
      try {
        const mapping = await this.processEvent(event, userId, localMappings);

        if (mapping) {
          result.mappings[event.localId] = mapping;
          localMappings.set(event.localId, mapping);
        }

        result.processed++;
      } catch (error: any) {
        this.logger.error(
          `Error processing event ${event.eventId}: ${error?.message ?? error}`,
        );
        result.failed++;
        result.errors.push({
          eventId: event.eventId,
          error: error?.message ?? "Unknown sync error",
        });
        result.success = false;
      }
    }

    this.logger.log(
      `Sync completed: ${result.processed} processed, ${result.failed} failed`,
    );
    return result;
  }

  private async processEvent(
    event: SyncEvent,
    userId: number | undefined,
    mappings: Map<string, any>,
  ): Promise<any> {
    switch (event.eventType) {
      case "CREATE_CLIENT":
        return this.handleCreateClient(event);
      case "UPDATE_CLIENT":
        return this.handleUpdateClient(event);
      case "CREATE_VEHICULO":
        return this.handleCreateVehiculo(event, userId);
      case "CREATE_OT":
        return this.handleCreateOT(event, userId);
      case "UPDATE_OT":
        return this.handleUpdateOT(event, userId, mappings);
      case "ADD_OT_PHOTO":
        return this.handleAddOTPhoto(event, mappings);
      case "ADD_OT_DAMAGE":
        return this.handleAddOTDamage(event, mappings);
      case "SET_OT_FIRMAS":
        return this.handleSetOTFirmas(event, mappings);
      case "CHANGE_OT_ESTADO":
        return this.handleChangeOTEstado(event, userId, mappings);
      case "UPLOAD_PDF":
        return this.handleUploadPdf(event, userId, mappings);
      case "CREATE_TIPO_VEHICULO":
        return this.handleCreateTipoVehiculo(event, userId);
      case "UPDATE_TIPO_VEHICULO":
        return this.handleUpdateTipoVehiculo(event, userId, mappings);
      case "INACTIVATE_TIPO_VEHICULO":
        return this.handleInactivateTipoVehiculo(event, userId, mappings);
      case "CREATE_COSA_TRAE":
        return this.handleCreateCosaTrae(event);
      case "UPDATE_COSA_TRAE":
        return this.handleUpdateCosaTrae(event, mappings);
      case "INACTIVATE_COSA_TRAE":
        return this.handleInactivateCosaTrae(event, mappings);
      case "CREATE_TIPO_DANO":
        return this.handleCreateTipoDano(event);
      case "UPDATE_TIPO_DANO":
        return this.handleUpdateTipoDano(event, mappings);
      case "INACTIVATE_TIPO_DANO":
        return this.handleInactivateTipoDano(event, mappings);
      default:
        throw new Error(`Unsupported event type: ${event.eventType}`);
    }
  }

  private async handleCreateClient(event: SyncEvent): Promise<any> {
    const cliente = await this.clientesService.create(event.payload);
    return { tarjeta: cliente.tarjeta };
  }

  private async handleUpdateClient(event: SyncEvent): Promise<any> {
    const { tarjeta, ...updateData } = event.payload;
    await this.clientesService.update(tarjeta, updateData);
    return { tarjeta };
  }

  private async handleCreateVehiculo(
    event: SyncEvent,
    userId?: number,
  ): Promise<any> {
    const vehiculo = await this.vehiculosService.create(event.payload, userId);
    return { serverId: vehiculo.id, placa: vehiculo.placa };
  }

  private async handleCreateOT(
    event: SyncEvent,
    userId?: number,
  ): Promise<any> {
    const ot = await this.otsService.create(event.payload, userId);
    return { serverId: ot.id, numeroOt: ot.numeroOt };
  }

  private async handleUpdateOT(
    event: SyncEvent,
    userId: number | undefined,
    mappings: Map<string, any>,
  ): Promise<any> {
    const { serverId, otId, otLocalId, ...updateData } = event.payload;
    const resolvedServerId = this.resolveMappedOtId(
      { serverId, otId, otLocalId },
      mappings,
    );

    const ot = await this.otsService.update(
      resolvedServerId,
      updateData,
      userId,
    );
    return { serverId: ot.id };
  }

  private async handleAddOTPhoto(
    event: SyncEvent,
    mappings: Map<string, any>,
  ): Promise<any> {
    const otId = this.resolveMappedOtId(event.payload, mappings);
    const { otLocalId: _otLocalId, ...fotoData } = event.payload;

    if (fotoData.tipo === "inspeccion") {
      fotoData.tipo = "documento";
    }

    if (!fotoData.url) {
      this.logger.warn(
        `Skipping ADD_OT_PHOTO without url for localId=${event.localId}`,
      );
      return { skipped: true };
    }

    const foto = await this.otsService.addFoto(otId, fotoData);
    return { fotoId: foto.id, otId };
  }

  private async handleAddOTDamage(
    event: SyncEvent,
    mappings: Map<string, any>,
  ): Promise<any> {
    const otId = this.resolveMappedOtId(event.payload, mappings);
    const { otLocalId: _otLocalId, ...danoData } = event.payload;

    if (!danoData.zona) {
      danoData.zona = "sin_zona";
    }

    const dano = await this.otsService.addDano(otId, danoData);
    return { danoId: dano.id, otId };
  }

  private async handleSetOTFirmas(
    event: SyncEvent,
    mappings: Map<string, any>,
  ): Promise<any> {
    const otId = this.resolveMappedOtId(event.payload, mappings);
    const { otLocalId: _otLocalId, ...firmasData } = event.payload;

    if (
      typeof firmasData.firmaCliente === "string" &&
      firmasData.firmaCliente.length > 500
    ) {
      this.logger.warn(
        `Skipping oversized firmaCliente for localId=${event.localId}`,
      );
      delete firmasData.firmaCliente;
    }

    if (
      typeof firmasData.firmaRecepcion === "string" &&
      firmasData.firmaRecepcion.length > 500
    ) {
      this.logger.warn(
        `Skipping oversized firmaRecepcion for localId=${event.localId}`,
      );
      delete firmasData.firmaRecepcion;
    }

    await this.otsService.saveFirmas(otId, firmasData);
    return { otId };
  }

  private async handleChangeOTEstado(
    event: SyncEvent,
    userId: number | undefined,
    mappings: Map<string, any>,
  ): Promise<any> {
    const otId = this.resolveMappedOtId(event.payload, mappings);
    const { estado, fechaSalida } = event.payload;

    const ot = await this.otsService.changeEstado(
      otId,
      estado,
      fechaSalida,
      userId,
    );
    return { serverId: ot.id };
  }

  private async handleUploadPdf(
    event: SyncEvent,
    userId: number | undefined,
    mappings: Map<string, any>,
  ): Promise<any> {
    const otId = this.resolveMappedOtId(event.payload, mappings);
    const pdfUrl = event.payload?.pdfUrl;

    if (!pdfUrl) {
      this.logger.warn(
        `Skipping UPLOAD_PDF without pdfUrl for localId=${event.localId}`,
      );
      return { skipped: true };
    }

    const ot = await this.otsService.savePdfEscaner(otId, pdfUrl, userId);
    return { serverId: ot.id };
  }

  private async handleCreateTipoVehiculo(
    event: SyncEvent,
    userId?: number,
  ): Promise<any> {
    const tipo = await this.catalogosService.createTipoVehiculo(
      event.payload,
      userId,
    );
    return { serverId: tipo.id, entityType: "catalog_tipo_vehiculo" };
  }

  private async handleUpdateTipoVehiculo(
    event: SyncEvent,
    userId: number | undefined,
    mappings: Map<string, any>,
  ): Promise<any> {
    const id = this.resolveCatalogId(event, mappings);
    await this.catalogosService.updateTipoVehiculo(id, event.payload, userId);
    return { serverId: id, entityType: "catalog_tipo_vehiculo" };
  }

  private async handleInactivateTipoVehiculo(
    event: SyncEvent,
    userId: number | undefined,
    mappings: Map<string, any>,
  ): Promise<any> {
    const id = this.resolveCatalogId(event, mappings);
    await this.catalogosService.inactivateTipoVehiculo(id, userId);
    return { serverId: id, entityType: "catalog_tipo_vehiculo" };
  }

  private async handleCreateCosaTrae(event: SyncEvent): Promise<any> {
    const item = await this.catalogosService.createCosaTrae(event.payload);
    return { serverId: item.id, entityType: "catalog_cosa_trae" };
  }

  private async handleUpdateCosaTrae(
    event: SyncEvent,
    mappings: Map<string, any>,
  ): Promise<any> {
    const id = this.resolveCatalogId(event, mappings);
    await this.catalogosService.updateCosaTrae(id, event.payload);
    return { serverId: id, entityType: "catalog_cosa_trae" };
  }

  private async handleInactivateCosaTrae(
    event: SyncEvent,
    mappings: Map<string, any>,
  ): Promise<any> {
    const id = this.resolveCatalogId(event, mappings);
    await this.catalogosService.inactivateCosaTrae(id);
    return { serverId: id, entityType: "catalog_cosa_trae" };
  }

  private async handleCreateTipoDano(event: SyncEvent): Promise<any> {
    const tipo = await this.catalogosService.createTipoDano(event.payload);
    return { serverId: tipo.id, entityType: "catalog_tipo_dano" };
  }

  private async handleUpdateTipoDano(
    event: SyncEvent,
    mappings: Map<string, any>,
  ): Promise<any> {
    const id = this.resolveCatalogId(event, mappings);
    await this.catalogosService.updateTipoDano(id, event.payload);
    return { serverId: id, entityType: "catalog_tipo_dano" };
  }

  private async handleInactivateTipoDano(
    event: SyncEvent,
    mappings: Map<string, any>,
  ): Promise<any> {
    const id = this.resolveCatalogId(event, mappings);
    await this.catalogosService.inactivateTipoDano(id);
    return { serverId: id, entityType: "catalog_tipo_dano" };
  }

  private resolveMappedOtId(payload: any, mappings: Map<string, any>): number {
    const directId = Number(payload?.otId ?? payload?.serverId);
    if (Number.isFinite(directId) && directId > 0) {
      return directId;
    }

    const otLocalId = payload?.otLocalId;
    if (otLocalId) {
      const mapped = mappings.get(otLocalId);
      const mappedId = Number(mapped?.serverId);
      if (Number.isFinite(mappedId) && mappedId > 0) {
        return mappedId;
      }
    }

    throw new Error(
      "Unable to resolve OT id (otId/serverId/otLocalId missing or unmapped)",
    );
  }

  private resolveCatalogId(
    event: SyncEvent,
    mappings: Map<string, any>,
  ): number {
    const payloadId = Number(event?.payload?.id);
    if (Number.isFinite(payloadId) && payloadId > 0) {
      return payloadId;
    }

    const eventMapped = mappings.get(event.localId);
    const mappedId = Number(eventMapped?.serverId);
    if (Number.isFinite(mappedId) && mappedId > 0) {
      return mappedId;
    }

    const parts = String(event.localId || "").split(":");
    const localParsedId = Number(parts[2]);
    if (Number.isFinite(localParsedId) && localParsedId > 0) {
      return localParsedId;
    }

    throw new Error(`Unable to resolve catalog id for ${event.localId}`);
  }

  async pullChanges(since: number): Promise<any> {
    const safeSince = Number.isFinite(Number(since)) ? Number(since) : 0;
    const sinceDate = safeSince > 0 ? new Date(safeSince) : new Date(0);

    const [ots, vehiculos, clientes, tiposVehiculo, tiposDano, cosasTrae] =
      await Promise.all([
        this.otRepository.find({
          where: safeSince > 0 ? { updatedAt: MoreThan(sinceDate) } : {},
          relations: [
            "vehiculo",
            "vehiculo.tipoVehiculo",
            "cosasTrae",
            "cosasTrae.cosaTrae",
            "danos",
            "danos.tipoDano",
            "fotos",
            "firma",
            "cotizaciones",
            "cotizaciones.lineas",
          ],
          order: { updatedAt: "DESC" },
        }),
        this.vehiculoRepository.find({
          where: safeSince > 0 ? { updatedAt: MoreThan(sinceDate) } : {},
          relations: ["tipoVehiculo"],
          order: { updatedAt: "DESC" },
        }),
        this.clienteLocalRepository.find({
          where: safeSince > 0 ? { updatedAt: MoreThan(sinceDate) } : {},
          order: { updatedAt: "DESC" },
        }),
        this.tipoVehiculoRepository.find({ order: { nombre: "ASC" } }),
        this.tipoDanoRepository.find({ order: { nombre: "ASC" } }),
        this.cosaTraeRepository.find({ order: { nombre: "ASC" } }),
      ]);

    return {
      timestamp: Date.now(),
      changes: {
        ots,
        vehiculos,
        clientes,
        catalogos: {
          tiposVehiculo,
          tiposDano,
          cosasTrae,
        },
      },
    };
  }
}

