import type { Pool } from 'pg';
import type { EventoFiscal, EventoCreate, EventoUpdate } from '@horux/shared';

export type RecurrenciaRecordatorio = 'unica' | 'mensual' | 'bimestral' | 'trimestral' | 'anual';

const RECURRENCIA_DELTA_MESES: Record<RecurrenciaRecordatorio, number> = {
  unica: 0,
  mensual: 1,
  bimestral: 2,
  trimestral: 3,
  anual: 12,
};

interface RecordatorioRow {
  id: number;
  titulo: string;
  descripcion: string | null;
  fecha_limite: Date;
  notas: string | null;
  completado: boolean;
  privado: boolean;
  creado_por: string;
  created_at: Date;
  recurrencia: RecurrenciaRecordatorio;
  serie_id: number | null;
  activo: boolean;
  fecha_inicio: Date | null;
  fecha_fin: Date | null;
}

function toISODate(d: Date): string {
  return d.toISOString().split('T')[0];
}

function startOfDay(d: Date): Date {
  const r = new Date(d);
  r.setHours(0, 0, 0, 0);
  return r;
}

function addMonthsLocal(date: Date, months: number): Date {
  const result = new Date(date);
  const day = result.getDate();
  result.setMonth(result.getMonth() + months);
  // Si el mes resultante no tiene el mismo día, volver al último día del mes anterior
  if (result.getDate() !== day) {
    result.setDate(0);
  }
  return startOfDay(result);
}

function calcularSiguienteFecha(fecha: Date, recurrencia: RecurrenciaRecordatorio): Date {
  return addMonthsLocal(fecha, RECURRENCIA_DELTA_MESES[recurrencia]);
}

function generarFechasInstancias(
  fechaInicio: Date,
  recurrencia: RecurrenciaRecordatorio,
  fechaFin: Date | null | undefined,
  maxMesesHorizonte = 24,
): Date[] {
  const delta = RECURRENCIA_DELTA_MESES[recurrencia];
  if (delta === 0) return [];

  const inicio = startOfDay(fechaInicio);
  const limiteHorizonte = addMonthsLocal(new Date(), maxMesesHorizonte);
  const limite = fechaFin ? startOfDay(fechaFin) : limiteHorizonte;

  const fechas: Date[] = [];
  let current = inicio;
  while (current <= limite) {
    fechas.push(new Date(current));
    current = calcularSiguienteFecha(current, recurrencia);
  }
  return fechas;
}

function rowToEventoFiscal(r: RecordatorioRow): EventoFiscal {
  return {
    id: r.id,
    titulo: r.titulo,
    descripcion: r.descripcion || '',
    tipo: 'custom' as const,
    fechaLimite: toISODate(r.fecha_limite),
    recurrencia: r.recurrencia,
    completado: r.completado,
    notas: r.notas,
    privado: r.privado,
    creadoPor: r.creado_por,
    createdAt: r.created_at?.toISOString(),
    // metadata extra para el frontend
    serieId: r.serie_id ?? undefined,
    esPeriodico: r.recurrencia !== 'unica',
  } as EventoFiscal;
}

async function findById(pool: Pool, id: number): Promise<RecordatorioRow | null> {
  const { rows } = await pool.query<RecordatorioRow>(
    `SELECT id, titulo, descripcion, fecha_limite, notas, completado, privado,
            creado_por, created_at, recurrencia, serie_id, activo, fecha_inicio, fecha_fin
     FROM recordatorios WHERE id = $1`,
    [id]
  );
  return rows[0] ?? null;
}

async function getMaestroDesdeId(pool: Pool, id: number): Promise<RecordatorioRow | null> {
  const row = await findById(pool, id);
  if (!row) return null;
  if (row.serie_id === null) return row;
  return findById(pool, row.serie_id);
}

async function generarInstancias(
  pool: Pool,
  maestro: RecordatorioRow,
  maxMesesHorizonte = 24,
): Promise<number> {
  if (!maestro.fecha_inicio) return 0;
  if (maestro.recurrencia === 'unica') return 0;

  const fechas = generarFechasInstancias(
    maestro.fecha_inicio,
    maestro.recurrencia,
    maestro.fecha_fin ?? undefined,
    maxMesesHorizonte,
  );

  if (fechas.length === 0) return 0;

  let insertadas = 0;
  for (const fecha of fechas) {
    const { rowCount } = await pool.query(
      `INSERT INTO recordatorios (
         titulo, descripcion, fecha_limite, notas, completado, privado,
         creado_por, recurrencia, serie_id, activo, fecha_inicio, fecha_fin
       )
       VALUES ($1, $2, $3, $4, false, $5, $6, 'unica', $7, true, $8, $9)
       ON CONFLICT DO NOTHING`,
      [
        maestro.titulo,
        maestro.descripcion,
        toISODate(fecha),
        maestro.notas,
        maestro.privado,
        maestro.creado_por,
        maestro.id,
        toISODate(maestro.fecha_inicio),
        maestro.fecha_fin ? toISODate(maestro.fecha_fin) : null,
      ]
    );
    insertadas += rowCount ?? 0;
  }
  return insertadas;
}

/**
 * Obtiene recordatorios visibles para el usuario.
 * - Públicos: todos los del tenant
 * - Privados: solo los creados por el usuario
 * Excluye los maestros periódicos (serie_id IS NULL AND recurrencia != 'unica')
 * para no duplicar eventos en el calendario.
 */
export async function getRecordatorios(
  pool: Pool,
  userId: string,
  año: number
): Promise<EventoFiscal[]> {
  const { rows } = await pool.query<RecordatorioRow>(`
    SELECT id, titulo, descripcion, fecha_limite, notas, completado, privado,
           creado_por, created_at, recurrencia, serie_id, activo, fecha_inicio, fecha_fin
    FROM recordatorios
    WHERE EXTRACT(YEAR FROM fecha_limite) = $1
      AND activo = true
      AND (privado = false OR creado_por = $2)
      AND NOT (serie_id IS NULL AND recurrencia <> 'unica')
    ORDER BY fecha_limite
  `, [año, userId]);

  return rows.map(rowToEventoFiscal);
}

export async function createRecordatorio(
  pool: Pool,
  userId: string,
  data: EventoCreate & { privado?: boolean; fechaFin?: string | null }
): Promise<EventoFiscal> {
  const recurrencia = (data.recurrencia as RecurrenciaRecordatorio) || 'unica';
  const fechaFin = data.fechaFin ? new Date(data.fechaFin + 'T00:00:00') : null;
  const fechaLimite = new Date(data.fechaLimite + 'T00:00:00');

  const { rows } = await pool.query<RecordatorioRow>(`
    INSERT INTO recordatorios (
      titulo, descripcion, fecha_limite, notas, completado, privado,
      creado_por, recurrencia, serie_id, activo, fecha_inicio, fecha_fin
    )
    VALUES ($1, $2, $3, $4, false, $5, $6, $7, NULL, true, $8, $9)
    RETURNING id, titulo, descripcion, fecha_limite, notas, completado, privado,
              creado_por, created_at, recurrencia, serie_id, activo, fecha_inicio, fecha_fin
  `, [
    data.titulo,
    data.descripcion || null,
    toISODate(fechaLimite),
    data.notas || null,
    data.privado ?? false,
    userId,
    recurrencia,
    toISODate(fechaLimite),
    fechaFin ? toISODate(fechaFin) : null,
  ]);

  const maestro = rows[0];

  if (recurrencia !== 'unica') {
    await generarInstancias(pool, maestro);
  }

  return rowToEventoFiscal(maestro);
}

export async function updateRecordatorio(
  pool: Pool,
  userId: string,
  id: number,
  data: EventoUpdate & { privado?: boolean }
): Promise<EventoFiscal | null> {
  const row = await findById(pool, id);
  if (!row) return null;

  // Completar una instancia individual de una serie periódica
  const soloCompletado =
    data.completado !== undefined &&
    data.titulo === undefined &&
    data.descripcion === undefined &&
    data.fechaLimite === undefined &&
    data.notas === undefined &&
    data.privado === undefined;

  if (soloCompletado && row.serie_id !== null) {
    await pool.query(`UPDATE recordatorios SET completado = $1, updated_at = NOW() WHERE id = $2`, [
      data.completado,
      id,
    ]);
    const updated = await findById(pool, id);
    return updated ? rowToEventoFiscal(updated) : null;
  }

  const maestro = row.serie_id === null ? row : await findById(pool, row.serie_id);
  if (!maestro) return null;

  // Recordatorio único: editar directamente
  if (maestro.recurrencia === 'unica') {
    const sets: string[] = [];
    const params: any[] = [];
    let idx = 1;

    if (data.titulo !== undefined) { sets.push(`titulo = $${idx++}`); params.push(data.titulo); }
    if (data.descripcion !== undefined) { sets.push(`descripcion = $${idx++}`); params.push(data.descripcion); }
    if (data.fechaLimite !== undefined) { sets.push(`fecha_limite = $${idx++}`); params.push(data.fechaLimite); }
    if (data.notas !== undefined) { sets.push(`notas = $${idx++}`); params.push(data.notas); }
    if (data.privado !== undefined) { sets.push(`privado = $${idx++}`); params.push(data.privado); }
    if (data.completado !== undefined) { sets.push(`completado = $${idx++}`); params.push(data.completado); }

    if (sets.length === 0) return rowToEventoFiscal(maestro);
    sets.push(`updated_at = NOW()`);
    params.push(maestro.id);

    const { rows } = await pool.query<RecordatorioRow>(`
      UPDATE recordatorios SET ${sets.join(', ')}
      WHERE id = $${idx}
      RETURNING id, titulo, descripcion, fecha_limite, notas, completado, privado,
                creado_por, created_at, recurrencia, serie_id, activo, fecha_inicio, fecha_fin
    `, params);

    return rows[0] ? rowToEventoFiscal(rows[0]) : null;
  }

  // Serie periódica: editar maestro y propagar a instancias futuras no completadas
  const updatesMaestro: string[] = [];
  const paramsMaestro: any[] = [];
  let idx = 1;

  if (data.titulo !== undefined) { updatesMaestro.push(`titulo = $${idx++}`); paramsMaestro.push(data.titulo); }
  if (data.descripcion !== undefined) { updatesMaestro.push(`descripcion = $${idx++}`); paramsMaestro.push(data.descripcion); }
  if (data.notas !== undefined) { updatesMaestro.push(`notas = $${idx++}`); paramsMaestro.push(data.notas); }
  if (data.privado !== undefined) { updatesMaestro.push(`privado = $${idx++}`); paramsMaestro.push(data.privado); }

  const nuevaFechaInicio = data.fechaLimite ? new Date(data.fechaLimite + 'T00:00:00') : null;
  if (nuevaFechaInicio) {
    updatesMaestro.push(`fecha_limite = $${idx++}`);
    paramsMaestro.push(toISODate(nuevaFechaInicio));
    updatesMaestro.push(`fecha_inicio = $${idx++}`);
    paramsMaestro.push(toISODate(nuevaFechaInicio));
  }

  if (updatesMaestro.length > 0) {
    updatesMaestro.push(`updated_at = NOW()`);
    paramsMaestro.push(maestro.id);
    await pool.query(
      `UPDATE recordatorios SET ${updatesMaestro.join(', ')} WHERE id = $${idx}`,
      paramsMaestro
    );
  }

  // Propagar campos de contenido a instancias futuras no completadas
  const updatesInstancias: string[] = [];
  const paramsInstancias: any[] = [];
  let iIdx = 1;

  if (data.titulo !== undefined) { updatesInstancias.push(`titulo = $${iIdx++}`); paramsInstancias.push(data.titulo); }
  if (data.descripcion !== undefined) { updatesInstancias.push(`descripcion = $${iIdx++}`); paramsInstancias.push(data.descripcion); }
  if (data.notas !== undefined) { updatesInstancias.push(`notas = $${iIdx++}`); paramsInstancias.push(data.notas); }
  if (data.privado !== undefined) { updatesInstancias.push(`privado = $${iIdx++}`); paramsInstancias.push(data.privado); }

  if (updatesInstancias.length > 0) {
    paramsInstancias.push(maestro.id);
    await pool.query(
      `UPDATE recordatorios
          SET ${updatesInstancias.join(', ')}
        WHERE serie_id = $${iIdx}
          AND fecha_limite >= CURRENT_DATE
          AND completado = false`,
      paramsInstancias
    );
  }

  // Si cambió la fecha de inicio, regenerar instancias futuras
  if (nuevaFechaInicio) {
    await pool.query(
      `DELETE FROM recordatorios
        WHERE serie_id = $1
          AND fecha_limite >= CURRENT_DATE
          AND completado = false`,
      [maestro.id]
    );
    const maestroActualizado = await findById(pool, maestro.id);
    if (maestroActualizado) {
      await generarInstancias(pool, maestroActualizado);
    }
  }

  const maestroFinal = await findById(pool, maestro.id);
  return maestroFinal ? rowToEventoFiscal(maestroFinal) : null;
}

export async function deleteRecordatorio(
  pool: Pool,
  userId: string,
  id: number
): Promise<boolean> {
  const row = await findById(pool, id);
  if (!row) return false;

  // Serie periódica: cancelar (desactivar maestro y borrar instancias futuras no completadas)
  if (row.recurrencia !== 'unica' || row.serie_id !== null) {
    const maestro = row.serie_id === null ? row : await findById(pool, row.serie_id);
    if (!maestro) return false;

    await pool.query(`UPDATE recordatorios SET activo = false, updated_at = NOW() WHERE id = $1`, [maestro.id]);
    await pool.query(
      `DELETE FROM recordatorios
        WHERE serie_id = $1
          AND fecha_limite >= CURRENT_DATE
          AND completado = false`,
      [maestro.id]
    );
    return true;
  }

  // Único: borrar directamente
  const { rowCount } = await pool.query(`DELETE FROM recordatorios WHERE id = $1`, [id]);
  return (rowCount ?? 0) > 0;
}

/**
 * Extiende las series activas para mantener un horizonte futuro de instancias.
 * Útil para llamar desde un cron periódico.
 */
export async function extenderSeriesActivas(
  pool: Pool,
  mesesHorizonte = 24,
): Promise<{ series: number; instancias: number }> {
  const { rows: maestros } = await pool.query<RecordatorioRow>(`
    SELECT id, titulo, descripcion, fecha_limite, notas, completado, privado,
           creado_por, created_at, recurrencia, serie_id, activo, fecha_inicio, fecha_fin
    FROM recordatorios
    WHERE serie_id IS NULL
      AND recurrencia <> 'unica'
      AND activo = true
  `);

  let seriesProcesadas = 0;
  let instanciasCreadas = 0;

  const horizonte = addMonthsLocal(new Date(), mesesHorizonte);

  for (const maestro of maestros) {
    const { rows: ultimas } = await pool.query<{ fecha_limite: Date }>(`
      SELECT fecha_limite
      FROM recordatorios
      WHERE serie_id = $1
      ORDER BY fecha_limite DESC
      LIMIT 1
    `, [maestro.id]);

    const ultima = ultimas[0]?.fecha_limite;
    if (!ultima || startOfDay(ultima) < addMonthsLocal(new Date(), mesesHorizonte - 6)) {
      const creadas = await generarInstancias(pool, maestro, mesesHorizonte);
      if (creadas > 0) {
        seriesProcesadas++;
        instanciasCreadas += creadas;
      }
    }
  }

  return { series: seriesProcesadas, instancias: instanciasCreadas };
}
