import Dexie, { Table } from 'dexie';
import { createClientId } from './client-id';

export interface DbOrdenTrabajo {
  localId: string;
  serverId?: number;
  numeroOt?: string;
  clienteTarjeta: string;
  vehiculo: any;
  fechaEntrada: Date;
  fechaSalida?: Date;
  km: number;
  nivelGasolina: number;
  kmSalida?: number | null;
  nivelGasolinaSalida?: number | null;
  recomendacionesEntrega?: string | null;
  cliente?: any;
  tienda?: any;
  requerimientoCliente?: string;
  observaciones?: string;
  estado: string;
  cosasTrae: number[];
  danos: any[];
  fotos: any[];
  firma?: any;
  mecanico?: any;
  mecanicoId?: number | null;
  cotizacion?: any;
  syncStatus: 'pending' | 'synced' | 'conflict';
  lastModified: number;
  version: number;
}

export interface DbCliente {
  tarjeta: string;
  nombre: string;
  nit?: string;
  direccion?: string;
  telefono?: string;
  celular?: string;
  email?: string;
  syncStatus: 'pending' | 'synced';
  lastModified: number;
}

export interface DbVehiculo {
  localId: string;
  serverId?: number;
  placa: string;
  marca?: string;
  modelo?: string;
  anio?: number;
  motor?: string;
  cilindraje?: string;
  color?: string;
  tipoVehiculoId: number;
  clienteId?: string;
  clienteIds?: string[];
  syncStatus: 'pending' | 'synced';
  lastModified: number;
}

export interface DbProducto {
  plu: string;
  desclarga: string;
  desccorta?: string;
  precio: number;
  iva: number;
  pagaiva: boolean;
  tipo: string;
  es_servicio: boolean;
  usainventario?: boolean;
  estaller?: boolean;
  origen?: 'local' | 'erp';
  cachedAt: number;
}

export interface DbCatalogo {
  id: number;
  tipo: 'tipo_vehiculo' | 'cosa_trae' | 'tipo_dano';
  nombre: string;
  descripcion?: string;
  extra?: any;
  cachedAt: number;
}

export interface DbSyncEvent {
  id?: number;
  eventId: string;
  eventType: string;
  entityType: string;
  localId: string;
  payload: any;
  timestamp: number;
  status: 'pending' | 'sent' | 'failed';
  retries: number;
  error?: string;
}

export interface DbSettings {
  key: string;
  value: any;
}

export interface DbOtPdf {
  id: string;
  otLocalId: string;
  fileName: string;
  blob: Blob;
  synced: boolean;
}

const INDEXED_DB_UNAVAILABLE_MESSAGE = 'IndexedDB solo esta disponible en el navegador';

class TallerDatabase extends Dexie {
  ordenesTrabajo!: Table<DbOrdenTrabajo>;
  clientes!: Table<DbCliente>;
  vehiculos!: Table<DbVehiculo>;
  productos!: Table<DbProducto>;
  catalogos!: Table<DbCatalogo>;
  syncEvents!: Table<DbSyncEvent>;
  settings!: Table<DbSettings>;
  otFotos!: Table<any>;
  otDanos!: Table<any>;
  otPdfs!: Table<DbOtPdf>;

  constructor() {
    super('TallerPWA');

    this.version(33).stores({
      ordenesTrabajo: 'localId, serverId, numeroOt, clienteTarjeta, estado, syncStatus, lastModified',
      clientes: 'tarjeta, nombre, syncStatus',
      vehiculos: 'localId, serverId, placa, clienteId, *clienteIds, syncStatus',
      productos: 'plu, desclarga, tipo, cachedAt',
      catalogos: '[tipo+id], tipo, cachedAt',
      syncEvents: '++id, eventId, eventType, status, timestamp',
      settings: 'key',
      otFotos: 'id, otLocalId, tipo, synced',
      otDanos: 'id, otLocalId, tipoDanoId, synced',
      otPdfs: 'id, otLocalId, synced',
    });
  }
}

function createUnavailableDbProxy(): TallerDatabase {
  const fail = () => {
    throw new Error(INDEXED_DB_UNAVAILABLE_MESSAGE);
  };

  return new Proxy(
    {},
    {
      get(_target, property) {
        if (property === 'open') {
          return async () => {
            throw new Error(INDEXED_DB_UNAVAILABLE_MESSAGE);
          };
        }

        if (property === 'close') {
          return () => undefined;
        }

        if (property === 'isOpen') {
          return () => false;
        }

        return fail;
      },
    },
  ) as TallerDatabase;
}

const hasIndexedDb = typeof window !== 'undefined' && typeof indexedDB !== 'undefined';

export function isOfflineDbAvailable(): boolean {
  return hasIndexedDb;
}

export const db: TallerDatabase = hasIndexedDb ? new TallerDatabase() : createUnavailableDbProxy();

if (hasIndexedDb) {
  db.open().catch(async (error: any) => {
    const message = String(error?.message || '');
    const isVersionError = error?.name === 'VersionError' || message.includes('VersionError');

    if (isVersionError) {
      console.warn('[db] Version mismatch detected. Resetting local IndexedDB schema...');
      db.close();
      await Dexie.delete('TallerPWA');
      window.location.reload();
      return;
    }

    throw error;
  });
}

export async function addSyncEvent(
  eventType: string,
  entityType: string,
  localId: string,
  payload: any
): Promise<void> {
  const eventId = createClientId('sync');

  await db.syncEvents.add({
    eventId,
    eventType,
    entityType,
    localId,
    payload,
    timestamp: Date.now(),
    status: 'pending',
    retries: 0,
  });
}

export async function getPendingSyncEvents(): Promise<DbSyncEvent[]> {
  return db.syncEvents
    .where('status')
    .equals('pending')
    .sortBy('timestamp');
}

export async function markEventSent(eventId: string): Promise<void> {
  await db.syncEvents
    .where('eventId')
    .equals(eventId)
    .modify({ status: 'sent' });
}

export async function markEventFailed(eventId: string, error: string): Promise<void> {
  await db.syncEvents
    .where('eventId')
    .equals(eventId)
    .modify((event) => {
      event.status = 'failed';
      event.retries = (event.retries || 0) + 1;
      event.error = error;
    });
}

export async function getLastSyncTimestamp(): Promise<number> {
  const setting = await db.settings.get('lastSyncTimestamp');
  return setting?.value || 0;
}

export async function setLastSyncTimestamp(timestamp: number): Promise<void> {
  await db.settings.put({ key: 'lastSyncTimestamp', value: timestamp });
}

// Esta base es UNA sola por navegador (Dexie no sabe nada de tenants) —
// guardaba OTs/clientes/vehiculos de CUALQUIER tenant con el que se haya
// logueado este dispositivo, sin distinguir uno de otro. Bug real,
// encontrado viendo datos de un tenant aparecer en el dashboard de otro
// recien creado: cambiar de tenant (switch o un login distinto en el mismo
// navegador) nunca vaciaba esto. `activeTenantId` es la unica fuente de
// verdad de "a que tenant pertenece lo que hay guardado ahora" — se compara
// SIEMPRE antes de confiar en el cache local.
const ACTIVE_TENANT_SETTING_KEY = 'activeTenantId';

export async function getActiveTenantId(): Promise<number | null> {
  if (!hasIndexedDb) return null;
  const setting = await db.settings.get(ACTIVE_TENANT_SETTING_KEY);
  return typeof setting?.value === 'number' ? setting.value : null;
}

async function hasPendingOfflineWork(): Promise<boolean> {
  const [pendingEvents, pendingOts, pendingClientes, pendingVehiculos] = await Promise.all([
    db.syncEvents.where('status').anyOf(['pending', 'failed']).count(),
    db.ordenesTrabajo.where('syncStatus').equals('pending').count(),
    db.clientes.where('syncStatus').equals('pending').count(),
    db.vehiculos.where('syncStatus').equals('pending').count(),
  ]);
  return pendingEvents + pendingOts + pendingClientes + pendingVehiculos > 0;
}

// Se llama en CADA login y en CADA switch de tenant, con el tenantId al que
// se acaba de entrar. Si el cache local ya era de este mismo tenant (o
// todavia no tiene ninguno asignado), no toca nada — sigue sirviendo de
// cache offline como siempre. Si es de OTRO tenant: si hay trabajo sin
// sincronizar todavia (alguien cargo fotos/firmas offline y no hubo señal
// para mandarlas), aborta con un error claro en vez de borrarlo en
// silencio — perder trabajo real de un mecanico es peor que el cache
// cruzado que esto arregla. Si no hay nada pendiente, vacia todo y arranca
// limpio para el tenant nuevo.
export async function ensureOfflineDbForTenant(tenantId: number): Promise<void> {
  if (!hasIndexedDb) return;

  const current = await getActiveTenantId();
  if (current === tenantId) return;

  if (current !== null) {
    const pending = await hasPendingOfflineWork();
    if (pending) {
      throw new Error(
        'Hay trabajo sin sincronizar del cliente anterior en este dispositivo (fotos, firmas u OTs offline). ' +
          'Conectate a internet y esperá a que termine de sincronizar (mirá el ícono de sincronización) antes de cambiar de cliente.',
      );
    }

    await Promise.all([
      db.ordenesTrabajo.clear(),
      db.clientes.clear(),
      db.vehiculos.clear(),
      db.productos.clear(),
      db.catalogos.clear(),
      db.syncEvents.clear(),
      db.otFotos.clear(),
      db.otDanos.clear(),
      db.otPdfs.clear(),
    ]);
    await db.settings.delete('lastSyncTimestamp');
  }

  await db.settings.put({ key: ACTIVE_TENANT_SETTING_KEY, value: tenantId });
}
