import { EstadoPresupuesto, TipoDocumento } from '@prisma/client';
import type { Request, Response } from 'express';
import prisma from '../config/db';
import { createFallbackBudget, listFallbackBudgets } from '../services/budget.store';
import { buildDocumentNumberFromSequence, nextDatabaseDocumentSequence } from '../services/document-sequence.service';
import { getBudgetDocumentSequenceType } from '../services/document-sequences';
import { resolveGenericTreatment, resolveOperationalUser } from '../services/system.resolvers';

const serializeBudgetItems = (
  items?: Array<{
    cantidad: number;
    precioUnitario: number;
    subtotal: number;
    tratamiento: { id?: string; nombre: string };
  }>,
) =>
  (items ?? []).map((item) => ({
    treatmentId: item.tratamiento.id ?? '',
    nombre: item.tratamiento.nombre,
    cantidad: item.cantidad,
    precioUnitario: item.precioUnitario,
    subtotal: item.subtotal,
  }));

const serializeBudget = (budget: {
  id: string;
  folio: string;
  fecha: Date | string;
  subtotal: number;
  igv: number;
  total: number;
  estado: EstadoPresupuesto;
  tipoDocumento: TipoDocumento | null;
  serieDoc?: string | null;
  correlativoDoc?: string | null;
  paciente?: { id: string; nombres: string; apellidos: string } | null;
  creadoPor?: { id: string; name: string } | null;
  items?: Array<{
    cantidad: number;
    precioUnitario: number;
    subtotal: number;
    tratamiento: { id?: string; nombre: string };
  }>;
}) => ({
  id: budget.id,
  folio: budget.folio,
  fecha: budget.fecha instanceof Date ? budget.fecha.toISOString() : budget.fecha,
  subtotal: budget.subtotal,
  igv: budget.igv,
  total: budget.total,
  estado: budget.estado,
  tipoDocumento: budget.tipoDocumento,
  serieDoc: budget.serieDoc ?? null,
  correlativoDoc: budget.correlativoDoc ?? null,
  concepto:
    budget.items && budget.items.length > 0
      ? budget.items.map((item) => item.tratamiento.nombre).join(' + ')
      : 'Presupuesto clinico',
  items: serializeBudgetItems(budget.items),
  paciente: {
    id: budget.paciente?.id ?? '',
    nombres: budget.paciente?.nombres ?? '',
    apellidos: budget.paciente?.apellidos ?? '',
  },
  creadoPor: {
    id: budget.creadoPor?.id ?? '',
    nombre: budget.creadoPor?.name ?? 'Administrador DentaFlow',
  },
});

export const listBudgets = async (_req: Request, res: Response) => {
  try {
    const budgets = await prisma.presupuesto.findMany({
      include: {
        paciente: true,
        creadoPor: true,
        items: {
          include: {
            tratamiento: true,
          },
        },
      },
      orderBy: { fecha: 'desc' },
    });

    res.setHeader('X-Data-Source', 'database');
    return res.json(budgets.map(serializeBudget));
  } catch (error) {
    console.warn('Database unavailable for budgets, using local fallback store.', error);
    const budgets = await listFallbackBudgets();
    res.setHeader('X-Data-Source', 'local-fallback');
    return res.json(
      budgets.map((budget) => ({
        id: budget.id,
        folio: budget.folio,
        fecha: budget.fecha,
        subtotal: budget.subtotal,
        igv: budget.igv,
        total: budget.total,
        estado: budget.estado,
        tipoDocumento: budget.tipoDocumento,
        serieDoc: budget.serieDoc,
        correlativoDoc: budget.correlativoDoc,
        concepto: budget.concepto,
        items: [],
        paciente: {
          id: budget.pacienteId,
          nombres: budget.pacienteNombres,
          apellidos: budget.pacienteApellidos,
        },
        creadoPor: {
          id: 'local-admin',
          nombre: budget.creadoPorNombre,
        },
      })),
    );
  }
};

export const createBudget = async (req: Request, res: Response) => {
  const { pacienteId, total, concepto, estado, tipoDocumento, items } = req.body;

  if (typeof pacienteId !== 'string') {
    return res.status(400).json({ error: 'Paciente es obligatorio' });
  }
  const normalizedStatus =
    typeof estado === 'string' && Object.values(EstadoPresupuesto).includes(estado as EstadoPresupuesto)
      ? (estado as EstadoPresupuesto)
      : EstadoPresupuesto.PENDIENTE;
  const normalizedDocument =
    typeof tipoDocumento === 'string' && Object.values(TipoDocumento).includes(tipoDocumento as TipoDocumento)
      ? (tipoDocumento as TipoDocumento)
      : TipoDocumento.BOLETA;

  const normalizedItems = Array.isArray(items)
    ? items
        .map((item) => ({
          treatmentId: typeof item?.treatmentId === 'string' ? item.treatmentId.trim() : '',
          cantidad:
            typeof item?.cantidad === 'number' && Number.isFinite(item.cantidad)
              ? Math.max(1, Math.round(item.cantidad))
              : 1,
          precioUnitario:
            typeof item?.precioUnitario === 'number' && Number.isFinite(item.precioUnitario)
              ? Number(item.precioUnitario.toFixed(2))
              : null,
        }))
        .filter((item) => item.treatmentId)
    : [];

  const normalizedConcept = typeof concepto === 'string' ? concepto.trim() : '';
  const normalizedTotal = typeof total === 'number' && Number.isFinite(total) ? Number(total.toFixed(2)) : null;

  if (normalizedItems.length === 0) {
    if (!normalizedConcept || normalizedTotal === null || normalizedTotal <= 0) {
      return res.status(400).json({ error: 'Debes registrar un tratamiento tarifado o un concepto con total valido.' });
    }
  }

  let fallbackConcept = normalizedConcept;
  let fallbackItems: Array<{
    treatmentId: string;
    nombre: string;
    cantidad: number;
    precioUnitario: number;
    subtotal: number;
  }> = [];
  let fallbackTotal = normalizedTotal ?? 0;

  try {
    const patient = await prisma.paciente.findUnique({
      where: { id: pacienteId },
    });

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

    const operator = await resolveOperationalUser();
    const resolvedItems =
      normalizedItems.length > 0
        ? await Promise.all(
            normalizedItems.map(async (item) => {
              const treatment = await prisma.tratamiento.findFirst({
                where: {
                  id: item.treatmentId,
                  pacienteId: null,
                  activo: true,
                },
              });

              if (!treatment) {
                const missingTreatmentError = new Error(`Tratamiento no encontrado: ${item.treatmentId}`);
                missingTreatmentError.name = 'TREATMENT_NOT_FOUND';
                throw missingTreatmentError;
              }

              const precioUnitario = item.precioUnitario ?? Number(treatment.costoBase.toFixed(2));
              return {
                treatment,
                cantidad: item.cantidad,
                precioUnitario,
                subtotal: Number((precioUnitario * item.cantidad).toFixed(2)),
              };
            }),
          )
        : [
            {
              treatment: await resolveGenericTreatment(normalizedConcept, Number((((normalizedTotal ?? 0) / 1.18)).toFixed(2))),
              cantidad: 1,
              precioUnitario: Number((((normalizedTotal ?? 0) / 1.18)).toFixed(2)),
              subtotal: Number((((normalizedTotal ?? 0) / 1.18)).toFixed(2)),
            },
          ];

    const subtotal = Number(resolvedItems.reduce((sum, item) => sum + item.subtotal, 0).toFixed(2));
    const computedTotal = Number((subtotal * 1.18).toFixed(2));
    const igv = Number((computedTotal - subtotal).toFixed(2));
    fallbackItems = resolvedItems.map((item) => ({
      treatmentId: item.treatment.id,
      nombre: item.treatment.nombre,
      cantidad: item.cantidad,
      precioUnitario: item.precioUnitario,
      subtotal: item.subtotal,
    }));
    fallbackConcept =
      normalizedItems.length > 0 ? resolvedItems.map((item) => item.treatment.nombre).join(' + ') : normalizedConcept;
    fallbackTotal = computedTotal;

    const budget = await prisma.$transaction(async (transaction) => {
      const folioSequence = await nextDatabaseDocumentSequence(transaction, 'BUDGET_FOLIO');
      const accountingSequence = await nextDatabaseDocumentSequence(
        transaction,
        getBudgetDocumentSequenceType(normalizedDocument),
      );
      const accountingNumber = buildDocumentNumberFromSequence(accountingSequence);
      const [serieDocValue, correlativoDocValue] = accountingNumber.split('-');
      const serieDoc = serieDocValue ?? null;
      const correlativoDoc = correlativoDocValue ?? null;

      return transaction.presupuesto.create({
        data: {
          folio: buildDocumentNumberFromSequence(folioSequence),
          subtotal,
          igv,
          total: computedTotal,
          estado: normalizedStatus,
          tipoDocumento: normalizedDocument,
          serieDoc,
          correlativoDoc,
          pacienteId,
          creadoPorId: operator.id,
          items: {
            create: resolvedItems.map((item) => ({
              cantidad: item.cantidad,
              precioUnitario: item.precioUnitario,
              descuento: 0,
              subtotal: item.subtotal,
              tratamientoId: item.treatment.id,
            })),
          },
        },
        include: {
          paciente: true,
          creadoPor: true,
          items: {
            include: {
              tratamiento: true,
            },
          },
        },
      });
    });

    res.setHeader('X-Data-Source', 'database');
    return res.status(201).json(serializeBudget(budget));
  } catch (error) {
    if (error instanceof Error && error.name === 'TREATMENT_NOT_FOUND') {
      return res.status(404).json({ error: error.message });
    }

    console.warn('Database unavailable while creating budget, using local fallback store.', error);

    try {
      const budget = await createFallbackBudget({
        pacienteId,
        total: fallbackTotal,
        concepto: fallbackConcept,
        estado: normalizedStatus,
        tipoDocumento: normalizedDocument,
      });

      res.setHeader('X-Data-Source', 'local-fallback');
      return res.status(201).json({
        id: budget.id,
        folio: budget.folio,
        fecha: budget.fecha,
        subtotal: budget.subtotal,
        igv: budget.igv,
        total: budget.total,
        estado: budget.estado,
        tipoDocumento: budget.tipoDocumento,
        serieDoc: budget.serieDoc,
        correlativoDoc: budget.correlativoDoc,
        concepto: budget.concepto,
        items: fallbackItems,
        paciente: {
          id: budget.pacienteId,
          nombres: budget.pacienteNombres,
          apellidos: budget.pacienteApellidos,
        },
        creadoPor: {
          id: 'local-admin',
          nombre: budget.creadoPorNombre,
        },
      });
    } catch (fallbackError) {
      console.error('Error creating budget:', fallbackError);
      return res.status(500).json({ error: 'Error interno del servidor' });
    }
  }
};
