import type { Prisma } from '@prisma/client';
import type { Request, Response } from 'express';
import prisma from '../config/db';
import { createFallbackDocumentIssue, listFallbackDocumentIssues, type StoredDocumentIssue } from '../services/document.store';
import { buildDocumentNumberFromSequence, nextDatabaseDocumentSequence } from '../services/document-sequence.service';
import { getFallbackClinicSettings, type ClinicSettings } from '../services/settings.store';
import { isDocumentSequenceType, type DocumentSequenceType } from '../services/document-sequences';

const normalizeText = (value: unknown) => (typeof value === 'string' ? value.trim() : '');

const parseOptionalText = (value: unknown) => {
  const normalizedValue = normalizeText(value);
  return normalizedValue || null;
};

const parseLimit = (value: unknown) => {
  const parsedValue = Number(value);
  return Number.isFinite(parsedValue) && parsedValue > 0 ? Math.min(parsedValue, 50) : 8;
};

const normalizeClinicSnapshot = async (value: unknown): Promise<ClinicSettings> => {
  const fallbackClinic = await getFallbackClinicSettings();

  if (!value || typeof value !== 'object' || Array.isArray(value)) {
    return fallbackClinic;
  }

  const clinic = value as Partial<ClinicSettings>;

  return {
    clinicName: normalizeText(clinic.clinicName) || fallbackClinic.clinicName,
    brandShortName: normalizeText(clinic.brandShortName) || fallbackClinic.brandShortName,
    slogan: normalizeText(clinic.slogan) || fallbackClinic.slogan,
    fiscalId: normalizeText(clinic.fiscalId) || fallbackClinic.fiscalId,
    location: normalizeText(clinic.location) || fallbackClinic.location,
    address: normalizeText(clinic.address) || fallbackClinic.address,
    contactEmail: normalizeText(clinic.contactEmail) || fallbackClinic.contactEmail,
    contactPhone: normalizeText(clinic.contactPhone) || fallbackClinic.contactPhone,
    websiteUrl: normalizeText(clinic.websiteUrl) || fallbackClinic.websiteUrl,
    whatsappPhone: normalizeText(clinic.whatsappPhone) || fallbackClinic.whatsappPhone,
    currency: normalizeText(clinic.currency) || fallbackClinic.currency,
    documentFooterNote: normalizeText(clinic.documentFooterNote) || fallbackClinic.documentFooterNote,
    defaultDocumentIssuer: normalizeText(clinic.defaultDocumentIssuer) || fallbackClinic.defaultDocumentIssuer,
    installSupportLabel: normalizeText(clinic.installSupportLabel) || fallbackClinic.installSupportLabel,
    installSupportValue: normalizeText(clinic.installSupportValue) || fallbackClinic.installSupportValue,
    androidApkUrl: normalizeText(clinic.androidApkUrl) || fallbackClinic.androidApkUrl,
    iosInstallUrl: normalizeText(clinic.iosInstallUrl) || fallbackClinic.iosInstallUrl,
    updatedAt: normalizeText(clinic.updatedAt) || fallbackClinic.updatedAt,
  };
};

const serializeDocumentIssue = (
  issue: Omit<StoredDocumentIssue, 'issuedAt' | 'createdAt'> & { issuedAt: string | Date; createdAt: string | Date },
) => ({
  id: issue.id,
  moduleScope: issue.moduleScope,
  documentType: issue.documentType,
  outputFormat: issue.outputFormat,
  documentNumber: issue.documentNumber,
  title: issue.title,
  statusLabel: issue.statusLabel,
  targetId: issue.targetId,
  patientName: issue.patientName,
  issuer: {
    id: issue.issuerUserId,
    name: issue.issuerName,
    role: issue.issuerRole,
  },
  logoLabel: issue.logoLabel,
  signatureLabel: issue.signatureLabel,
  clinicSnapshot: issue.clinicSnapshot,
  payloadSnapshot: issue.payloadSnapshot,
  metadata: issue.metadata,
  issuedAt: issue.issuedAt instanceof Date ? issue.issuedAt.toISOString() : issue.issuedAt,
  createdAt: issue.createdAt instanceof Date ? issue.createdAt.toISOString() : issue.createdAt,
});

export const listDocumentHistory = async (req: Request, res: Response) => {
  const moduleScope = parseOptionalText(req.query.moduleScope);
  const documentType = parseOptionalText(req.query.documentType);
  const targetId = parseOptionalText(req.query.targetId);
  const limit = parseLimit(req.query.limit);

  try {
    const items = await prisma.documentIssue.findMany({
      where: {
        ...(moduleScope ? { moduleScope } : {}),
        ...(documentType ? { documentType } : {}),
        ...(targetId ? { targetId } : {}),
      },
      orderBy: { issuedAt: 'desc' },
      take: limit,
    });

    res.setHeader('X-Data-Source', 'database');
    return res.json(items.map((item) => serializeDocumentIssue({
      ...item,
      targetId: item.targetId ?? null,
      patientName: item.patientName ?? null,
      issuerUserId: item.issuerUserId ?? null,
      clinicSnapshot: item.clinicSnapshot as ClinicSettings,
      payloadSnapshot: item.payloadSnapshot,
      metadata: (item.metadata as Record<string, unknown> | null) ?? null,
    })));
  } catch (error) {
    console.warn('Database unavailable for document history, using local fallback store.', error);
    const items = await listFallbackDocumentIssues({
      ...(moduleScope ? { moduleScope } : {}),
      ...(documentType ? { documentType } : {}),
      ...(targetId ? { targetId } : {}),
      limit,
    });
    res.setHeader('X-Data-Source', 'local-fallback');
    return res.json(items.map(serializeDocumentIssue));
  }
};

export const issueDocument = async (req: Request, res: Response) => {
  const moduleScope = normalizeText(req.body.moduleScope);
  const documentType = normalizeText(req.body.documentType);
  const outputFormat = normalizeText(req.body.outputFormat).toUpperCase();
  const title = normalizeText(req.body.title);
  const statusLabel = normalizeText(req.body.statusLabel);
  const targetId = parseOptionalText(req.body.targetId);
  const patientName = parseOptionalText(req.body.patientName);
  const logoLabel = normalizeText(req.body.logoLabel);
  const signatureLabel = normalizeText(req.body.signatureLabel);
  const payloadSnapshot = req.body.payloadSnapshot;
  const issuedByValue = req.body.issuedBy;
  const metadataValue = req.body.metadata;

  if (!moduleScope || !documentType || !outputFormat || !title || !statusLabel || payloadSnapshot === undefined) {
    return res.status(400).json({ error: 'Datos de emision incompletos' });
  }

  if (!isDocumentSequenceType(documentType)) {
    return res.status(400).json({ error: 'Tipo de documento no soportado' });
  }

  const issuedBy =
    issuedByValue && typeof issuedByValue === 'object' && !Array.isArray(issuedByValue)
      ? issuedByValue as { id?: unknown; name?: unknown; role?: unknown }
      : null;

  const clinicSnapshot = await normalizeClinicSnapshot(req.body.clinicSnapshot);
  const issuerUserId = parseOptionalText(issuedBy?.id);
  const issuerName = normalizeText(issuedBy?.name) || 'Usuario DentaFlow';
  const issuerRole = normalizeText(issuedBy?.role) || 'ADMIN';
  const issueDate = new Date();
  const metadata =
    metadataValue && typeof metadataValue === 'object' && !Array.isArray(metadataValue)
      ? (metadataValue as Record<string, unknown>)
      : null;

  try {
    const record = await prisma.$transaction(async (transaction) => {
      const sequence = await nextDatabaseDocumentSequence(transaction, documentType as DocumentSequenceType, issueDate);

      return transaction.documentIssue.create({
        data: {
          moduleScope,
          documentType,
          outputFormat,
          documentNumber: buildDocumentNumberFromSequence(sequence),
          title,
          statusLabel,
          targetId,
          patientName,
          issuerUserId,
          issuerName,
          issuerRole,
          logoLabel: logoLabel || clinicSnapshot.clinicName,
          signatureLabel: signatureLabel || issuerName,
          clinicSnapshot: clinicSnapshot as Prisma.InputJsonValue,
          payloadSnapshot: payloadSnapshot as Prisma.InputJsonValue,
          metadata: metadata as Prisma.InputJsonValue,
          issuedAt: issueDate,
        },
      });
    });

    res.setHeader('X-Data-Source', 'database');
    return res.status(201).json(
      serializeDocumentIssue({
        ...record,
        targetId: record.targetId ?? null,
        patientName: record.patientName ?? null,
        issuerUserId: record.issuerUserId ?? null,
        clinicSnapshot: record.clinicSnapshot as ClinicSettings,
        payloadSnapshot: record.payloadSnapshot,
        metadata: (record.metadata as Record<string, unknown> | null) ?? null,
      }),
    );
  } catch (error) {
    console.warn('Database unavailable while issuing document, using local fallback store.', error);

    try {
      const record = await createFallbackDocumentIssue({
        sequenceType: documentType as DocumentSequenceType,
        moduleScope,
        documentType,
        outputFormat,
        title,
        statusLabel,
        targetId,
        patientName,
        issuerUserId,
        issuerName,
        issuerRole,
        logoLabel: logoLabel || clinicSnapshot.clinicName,
        signatureLabel: signatureLabel || issuerName,
        clinicSnapshot,
        payloadSnapshot,
        metadata,
        issuedAt: issueDate.toISOString(),
      });

      res.setHeader('X-Data-Source', 'local-fallback');
      return res.status(201).json(serializeDocumentIssue(record));
    } catch (fallbackError) {
      console.error('Error issuing document:', fallbackError);
      return res.status(500).json({ error: 'Error interno del servidor' });
    }
  }
};
