import { TipoTransaccion } from '@prisma/client';
import type { Request, Response } from 'express';
import prisma from '../config/db';
import { buildDocumentNumberFromSequence, nextDatabaseDocumentSequence } from '../services/document-sequence.service';
import { getFinanceDocumentSequenceType } from '../services/document-sequences';
import { createFallbackTransaction, listFallbackTransactions } from '../services/finance.store';
import { resolveCashPatient } from '../services/system.resolvers';

const serializeTransaction = (transaction: {
  id: string;
  referencia: string | null;
  descripcion: string | null;
  monto: number;
  tipo: TipoTransaccion;
  fecha: Date;
  paciente: { id: string; nombres: string; apellidos: string };
}) => ({
  id: transaction.id,
  referencia:
    transaction.referencia ??
    `${transaction.tipo === TipoTransaccion.INGRESO ? 'CI-LEGACY' : 'CE-LEGACY'}-${transaction.id.slice(0, 6).toUpperCase()}`,
  monto: transaction.monto,
  tipo: transaction.tipo,
  fecha: transaction.fecha.toISOString(),
  descripcion: transaction.descripcion ?? (transaction.tipo === TipoTransaccion.INGRESO ? 'Pago registrado' : 'Movimiento de caja'),
  paciente: {
    id: transaction.paciente.id,
    nombres: transaction.paciente.nombres,
    apellidos: transaction.paciente.apellidos,
  },
});

const buildSummary = (transactions: Array<{ monto: number; tipo: TipoTransaccion; fecha: string | Date }>) => {
  const ingresos = transactions
    .filter((transaction) => transaction.tipo === TipoTransaccion.INGRESO)
    .reduce((sum, transaction) => sum + transaction.monto, 0);
  const egresos = transactions
    .filter((transaction) => transaction.tipo === TipoTransaccion.EGRESO)
    .reduce((sum, transaction) => sum + transaction.monto, 0);

  const today = new Date();
  today.setHours(0, 0, 0, 0);

  const movementsToday = transactions.filter((transaction) => {
    const currentDate = new Date(transaction.fecha);
    return currentDate >= today;
  }).length;

  return {
    ingresos,
    egresos,
    balance: ingresos - egresos,
    movimientosHoy: movementsToday,
  };
};

export const getFinanceSummary = async (_req: Request, res: Response) => {
  try {
    const transactions = await prisma.transaccion.findMany({
      include: {
        paciente: true,
      },
      orderBy: { fecha: 'desc' },
      take: 20,
    });

    const serializedTransactions = transactions.map(serializeTransaction);

    res.setHeader('X-Data-Source', 'database');
    return res.json({
      kpis: buildSummary(serializedTransactions),
      transactions: serializedTransactions,
    });
  } catch (error) {
    console.warn('Database unavailable for finances, using local fallback store.', error);
    const transactions = await listFallbackTransactions();
    res.setHeader('X-Data-Source', 'local-fallback');
    return res.json({
      kpis: buildSummary(transactions),
      transactions: transactions.map((transaction) => ({
        id: transaction.id,
        referencia: transaction.referencia,
        monto: transaction.monto,
        tipo: transaction.tipo,
        fecha: transaction.fecha,
        descripcion: transaction.descripcion,
        paciente: {
          id: transaction.pacienteId,
          nombres: transaction.pacienteNombres,
          apellidos: transaction.pacienteApellidos,
        },
      })),
    });
  }
};

export const createTransaction = async (req: Request, res: Response) => {
  const { monto, tipo, descripcion, pacienteId } = req.body;

  if (typeof monto !== 'number' || !Number.isFinite(monto) || monto <= 0) {
    return res.status(400).json({ error: 'El monto debe ser mayor a cero' });
  }

  const normalizedType =
    typeof tipo === 'string' && Object.values(TipoTransaccion).includes(tipo as TipoTransaccion)
      ? (tipo as TipoTransaccion)
      : TipoTransaccion.INGRESO;

  try {
    const resolvedPatient =
      typeof pacienteId === 'string' && pacienteId.trim()
        ? await prisma.paciente.findUnique({ where: { id: pacienteId } })
        : await resolveCashPatient();

    if (!resolvedPatient) {
      return res.status(404).json({ error: 'Paciente no encontrado' });
    }

    const transaction = await prisma.$transaction(async (databaseTransaction) => {
      const sequence = await nextDatabaseDocumentSequence(
        databaseTransaction,
        getFinanceDocumentSequenceType(normalizedType),
      );

      return databaseTransaction.transaccion.create({
        data: {
          referencia: buildDocumentNumberFromSequence(sequence),
          descripcion: typeof descripcion === 'string' && descripcion.trim() ? descripcion.trim() : null,
          monto,
          tipo: normalizedType,
          pacienteId: resolvedPatient.id,
        },
        include: {
          paciente: true,
        },
      });
    });

    const serializedTransaction = serializeTransaction(transaction);

    res.setHeader('X-Data-Source', 'database');
    return res.status(201).json(serializedTransaction);
  } catch (error) {
    console.warn('Database unavailable while creating transaction, using local fallback store.', error);

    try {
      const fallbackPayload: {
        monto: number;
        tipo: TipoTransaccion;
        descripcion?: string;
        pacienteId?: string;
      } = {
        monto,
        tipo: normalizedType,
      };

      if (typeof descripcion === 'string' && descripcion.trim()) {
        fallbackPayload.descripcion = descripcion;
      }

      if (typeof pacienteId === 'string' && pacienteId.trim()) {
        fallbackPayload.pacienteId = pacienteId;
      }

      const transaction = await createFallbackTransaction({
        ...fallbackPayload,
      });

      res.setHeader('X-Data-Source', 'local-fallback');
      return res.status(201).json({
        id: transaction.id,
        referencia: transaction.referencia,
        monto: transaction.monto,
        tipo: transaction.tipo,
        fecha: transaction.fecha,
        descripcion: transaction.descripcion,
        paciente: {
          id: transaction.pacienteId,
          nombres: transaction.pacienteNombres,
          apellidos: transaction.pacienteApellidos,
        },
      });
    } catch (fallbackError) {
      console.error('Error creating transaction:', fallbackError);
      return res.status(500).json({ error: 'Error interno del servidor' });
    }
  }
};
