/*
|--------------------------------------------------------------------------
| Agência PSI
|--------------------------------------------------------------------------
| Criado e desenvolvido por Leonardo Nohama
|
| Tecnologia aplicada à escuta.
| Estrutura para o cuidado.
|
| Arquivo: src/services/DocumentGenerate.service.js
| Data: 2026
| Local: São Carlos/SP — Brasil
|--------------------------------------------------------------------------
| © 2026 — Todos os direitos reservados
|--------------------------------------------------------------------------
*/
import { supabase } from '@/lib/supabase/client';
import { tenantDb } from '@/lib/supabase/tenantClient';
import { valorExtenso } from '@/utils/valorExtenso';

const BUCKET = 'generated-docs';

// Mapa tipo do template → tipo_documento (CHECK constraint de documents).
// Recibos vão pra 'recibo'; demais ficam em 'outro' (CHECK não tem entradas
// específicas pra cada consent form, mas 'outro' é válido).
const TEMPLATE_TYPE_TO_DOC_TYPE = {
    recibo_pagamento: 'recibo',
    declaracao_comparecimento: 'declaracao',
    declaracao_inicio_tratamento: 'declaracao',
    atestado_psicologico: 'atestado',
    relatorio_acompanhamento: 'relatorio_externo',
    laudo_psicologico: 'laudo',
    parecer_psicologico: 'laudo',
    encaminhamento: 'declaracao'
};

function mapTipoDocumento(templateTipo) {
    return TEMPLATE_TYPE_TO_DOC_TYPE[templateTipo] || 'outro';
}

/**
 * Formata o registro profissional: "CRP 12345/SP".
 * Funciona com qualquer conselho (CRP, CRM, CRFa, CREFITO etc).
 */
function formatRegistroProfissional({ tipo, numero, uf }) {
    if (!tipo || !numero) return '';
    const parts = [tipo, numero];
    if (uf) parts[1] = `${numero}/${uf}`;
    return parts.join(' ');
}

// ── Helpers ──────────────────────────────────────────────────

async function getOwnerId() {
    const { data, error } = await supabase.auth.getUser();
    if (error) throw error;
    const uid = data?.user?.id;
    if (!uid) throw new Error('Sessão inválida.');
    return uid;
}

async function getActiveTenantId(uid) {
    const { data, error } = await supabase
        .from('tenant_members')
        .select('tenant_id')
        .eq('user_id', uid)
        .eq('status', 'active')
        .order('created_at', { ascending: false })
        .limit(1)
        .single();
    if (error) throw error;
    if (!data?.tenant_id) throw new Error('Tenant não encontrado.');
    return data.tenant_id;
}

// ── Carregar dados para preenchimento ───────────────────────

/**
 * Busca dados do paciente para preencher variaveis do template.
 */
export async function loadPatientData(patientId) {
    const { data, error } = await tenantDb().from('patients')
        .select(`
            nome_completo, nome_social, cpf, data_nascimento,
            telefone, email_principal,
            endereco, numero, bairro, cidade, estado, cep
        `)
        .eq('id', patientId)
        .single();

    if (error) throw error;

    const p = data;
    const endereco = [p.endereco, p.numero, p.bairro, p.cidade, p.estado]
        .filter(Boolean).join(', ');

    return {
        paciente_nome: p.nome_completo || '',
        paciente_nome_social: p.nome_social || '',
        paciente_cpf: p.cpf || '',
        paciente_data_nascimento: p.data_nascimento
            ? new Date(p.data_nascimento).toLocaleDateString('pt-BR')
            : '',
        paciente_telefone: p.telefone || '',
        paciente_email: p.email_principal || '',
        paciente_endereco: endereco
    };
}

/**
 * Busca dados da sessao (agenda_evento) para preencher variaveis.
 */
export async function loadSessionData(agendaEventoId) {
    if (!agendaEventoId) return {};

    const { data, error } = await tenantDb().from('agenda_eventos')
        .select('inicio_em, fim_em, modalidade, price')
        .eq('id', agendaEventoId)
        .single();

    if (error) return {};

    const s = data;
    const inicio = s.inicio_em ? new Date(s.inicio_em) : null;
    const fim = s.fim_em ? new Date(s.fim_em) : null;

    return {
        data_sessao: inicio ? inicio.toLocaleDateString('pt-BR') : '',
        hora_inicio: inicio ? inicio.toLocaleTimeString('pt-BR', { hour: '2-digit', minute: '2-digit' }) : '',
        hora_fim: fim ? fim.toLocaleTimeString('pt-BR', { hour: '2-digit', minute: '2-digit' }) : '',
        modalidade: s.modalidade || '',
        valor: s.price ? `R$ ${Number(s.price).toFixed(2).replace('.', ',')}` : ''
    };
}

/**
 * Busca dados do terapeuta (profile + tenant_member).
 *
 * Inclui registro profissional (CRP/CRM/CRFa etc) adicionado pela migration
 * 20260521000003. Retorna:
 *   - terapeuta_registro: "CRP 12345/SP" auto-formatado (genérico p/ qualquer conselho)
 *   - terapeuta_crp:      compat — só preenchido se o tipo for CRP (templates legacy)
 *   - terapeuta_registro_tipo / _numero / _uf: campos individuais p/ uso fino
 */
export async function loadTherapistData() {
    const ownerId = await getOwnerId();

    const { data: profile } = await supabase
        .from('profiles')
        .select('full_name, phone, professional_registration_type, professional_registration_type_other, professional_registration_number, professional_registration_uf')
        .eq('id', ownerId)
        .single();

    // Email vem de auth.users (nao existe em profiles)
    const { data: userData } = await supabase.auth.getUser();
    const email = userData?.user?.email || '';

    const tipoRaw = profile?.professional_registration_type || '';
    const tipoOther = profile?.professional_registration_type_other || '';
    // Quando type='outro', usa o nome livre do conselho/instituição
    const tipo = tipoRaw === 'outro' ? tipoOther : tipoRaw;
    const numero = profile?.professional_registration_number || '';
    const uf     = profile?.professional_registration_uf || '';
    const registro = formatRegistroProfissional({ tipo, numero, uf });

    return {
        terapeuta_nome: profile?.full_name || '',
        terapeuta_email: email,
        terapeuta_telefone: profile?.phone || '',
        terapeuta_registro: registro,
        terapeuta_registro_tipo: tipo,
        terapeuta_registro_numero: numero,
        terapeuta_registro_uf: uf,
        // Compat: templates antigos referenciam {{terapeuta_crp}} — preenche só
        // o número/UF (sem prefixo) pra não duplicar com o "CRP" já no HTML.
        // Quando o registro não é CRP, retorna vazio (template visualmente errado
        // pede pra usar {{terapeuta_registro}}).
        terapeuta_crp: tipoRaw === 'CRP' ? (uf ? `${numero}/${uf}` : numero) : ''
    };
}

/**
 * Busca dados da clinica (tenant).
 *
 * `tenants.cpf_cnpj` aceita 11 (CPF) ou 14 (CNPJ) dígitos. Pra `clinica_cnpj`
 * só preenche se tiver 14 dígitos (formatado XX.XXX.XXX/XXXX-XX).
 */
export async function loadClinicData(tenantId) {
    const { data: tenant } = await supabase
        .from('tenants')
        .select('*')
        .eq('id', tenantId)
        .maybeSingle();

    if (!tenant) {
        return { clinica_nome: '', clinica_endereco: '', clinica_telefone: '', clinica_cnpj: '' };
    }

    const endereco = tenant.logradouro
        ? [tenant.logradouro, tenant.numero, tenant.bairro, tenant.cidade, tenant.estado]
            .filter(Boolean).join(', ')
        : tenant.address || '';

    const digits = String(tenant.cpf_cnpj || '').replace(/\D/g, '');
    let cnpj = '';
    if (digits.length === 14) {
        cnpj = digits.replace(/^(\d{2})(\d{3})(\d{3})(\d{4})(\d{2})$/, '$1.$2.$3/$4-$5');
    } else if (digits.length === 11) {
        cnpj = digits.replace(/^(\d{3})(\d{3})(\d{3})(\d{2})$/, '$1.$2.$3-$4');
    }

    return {
        clinica_nome: tenant.name || '',
        clinica_endereco: endereco,
        clinica_telefone: tenant.phone || '',
        clinica_cnpj: cnpj
    };
}

// ── Montar dados gerais ─────────────────────────────────────

function getDateVariables() {
    const now = new Date();
    const meses = [
        'janeiro', 'fevereiro', 'março', 'abril', 'maio', 'junho',
        'julho', 'agosto', 'setembro', 'outubro', 'novembro', 'dezembro'
    ];
    return {
        data_atual: now.toLocaleDateString('pt-BR'),
        data_atual_extenso: `${now.getDate()} de ${meses[now.getMonth()]} de ${now.getFullYear()}`
    };
}

/**
 * Carrega todos os dados necessarios para preencher um template.
 *
 * @param {string} patientId
 * @param {string|null} agendaEventoId  - sessão p/ data_sessao + valor
 * @param {object} extras               - overrides ad-hoc (ex: { valor: 150, forma_pagamento: 'PIX' })
 */
export async function loadAllVariables(patientId, agendaEventoId = null, extras = {}) {
    const ownerId = await getOwnerId();
    const tenantId = await getActiveTenantId(ownerId);

    // Promise.allSettled pra nao mascarar falha individual: se uma source
    // falhar, as outras ainda preenchem e a gente loga qual quebrou.
    const results = await Promise.allSettled([
        loadPatientData(patientId),
        loadSessionData(agendaEventoId),
        loadTherapistData(),
        loadClinicData(tenantId)
    ]);
    const labels = ['patient', 'session', 'therapist', 'clinic'];
    const errors = [];
    results.forEach((r, i) => {
        if (r.status === 'rejected') {
            errors.push({ source: labels[i], err: r.reason });
            console.error(`[loadAllVariables] falha em ${labels[i]}:`, r.reason);
        }
    });
    const [patient, session, therapist, clinic] = results.map(r => r.status === 'fulfilled' ? r.value : {});
    if (import.meta?.env?.DEV) {
        console.log('[loadAllVariables] resultados:', {
            patient, session, therapist, clinic,
            ownerId, tenantId, patientId, agendaEventoId,
            errors
        });
    }

    // Resolve valor numérico (extras tem prioridade sobre session).
    // Number.isFinite (strict) em vez de isFinite global: este último coerce
    // null pra 0 e retorna true, fazendo null.toFixed crashar logo abaixo.
    const valorNum = extras.valor != null
        ? Number(extras.valor)
        : (session.valor ? Number(String(session.valor).replace(/[R$\s.]/g, '').replace(',', '.')) : null);

    const valorFormatted = Number.isFinite(valorNum)
        ? `R$ ${valorNum.toFixed(2).replace('.', ',')}`
        : (session.valor || '');

    const valorExtensoStr = Number.isFinite(valorNum) ? valorExtenso(valorNum) : '';

    const merged = {
        ...patient,
        ...session,
        ...therapist,
        ...clinic,
        ...getDateVariables(),
        // Overrides explícitos (forma_pagamento, valor, qualquer outra chave)
        ...extras,
        // Computados — sempre rebaixam extras nos campos que controlamos
        valor: valorFormatted,
        valor_extenso: valorExtensoStr,
        forma_pagamento: extras.forma_pagamento || session.forma_pagamento || '',
        cidade_estado: clinic.clinica_endereco
            ? `${clinic.clinica_endereco.split(', ').slice(-2).join('/')}`
            : ''
    };

    return merged;
}

// ── Preencher template ──────────────────────────────────────

/**
 * Substitui {{variavel}} no HTML pelos valores fornecidos.
 */
export function fillTemplate(html, variables = {}) {
    return String(html || '').replace(/\{\{(\w+)\}\}/g, (match, key) => {
        return variables[key] !== undefined ? String(variables[key]) : match;
    });
}

/**
 * Monta o HTML completo do documento (cabecalho + corpo + rodape).
 */
export function buildFullHtml(template, variables = {}) {
    const cabecalho = fillTemplate(template.cabecalho_html || '', variables);
    const corpo = fillTemplate(template.corpo_html || '', variables);
    const rodape = fillTemplate(template.rodape_html || '', variables);

    return `
<!DOCTYPE html>
<html lang="pt-BR" style="color-scheme:light;">
<head>
<meta charset="UTF-8">
<style>
  *, *::before, *::after { color-scheme: light; }
  @page { size: A4; margin: 20mm 15mm 25mm 15mm; }
  html, body {
    all: initial;
    font-family: 'Segoe UI', Arial, sans-serif;
    font-size: 12pt;
    line-height: 1.6;
    color: #1a1a1a;
    background: #ffffff;
    -webkit-print-color-adjust: exact;
  }
  h1, h2, h3, h4, p, ul, ol, li, table, tr, td, th, div, span, strong, em, hr, a {
    all: revert;
    color: inherit;
    font-family: inherit;
  }
  h2 { font-size: 16pt; margin-bottom: 16px; }
  h3 { font-size: 13pt; margin-top: 20px; margin-bottom: 8px; }
  p { margin: 8px 0; }
  table { border-collapse: collapse; }
  td { padding: 4px 8px; }
  hr { border: none; border-top: 1px solid #333333; }
  a { color: #2563eb; }
  .doc-header { text-align: center; margin-bottom: 24px; padding-bottom: 12px; border-bottom: 1px solid #cccccc; }
  .doc-header img { max-height: 60px; margin-bottom: 8px; }
  .doc-content { min-height: 600px; }
  .doc-footer { margin-top: 40px; padding-top: 12px; border-top: 1px solid #cccccc; font-size: 10pt; color: #666666; text-align: center; }
  .signature-line { margin-top: 60px; text-align: center; }
  .signature-line hr { width: 250px; margin: 0 auto 4px; border: none; border-top: 1px solid #333333; }
</style>
</head>
<body>
  <div class="doc-header">${cabecalho}</div>
  <div class="doc-content">${corpo}</div>
  <div class="doc-footer">${rodape}</div>
</body>
</html>`.trim();
}

// ── Gerar PDF (jsPDF + html2canvas via pdf.service) ────────

import { htmlToPdfBlob, htmlToPdfDownload, htmlToPdfOpen } from '@/services/pdf.service';

/**
 * Gera um Blob PDF a partir do template preenchido.
 */
export async function generatePdfBlob(template, variables = {}) {
    const html = buildFullHtml(template, variables);
    return await htmlToPdfBlob(html);
}

/**
 * Gera PDF e dispara download automatico.
 */
export async function generateAndDownloadPdf(template, variables = {}, filename = 'documento.pdf') {
    const html = buildFullHtml(template, variables);
    await htmlToPdfDownload(html, filename);
}

/**
 * Abre o PDF em nova aba para impressao.
 */
export async function printDocument(template, variables = {}) {
    const html = buildFullHtml(template, variables);
    await htmlToPdfOpen(html);
}

// ── Salvar documento gerado ─────────────────────────────────

/**
 * Registra um documento gerado na tabela document_generated.
 * O PDF deve ser passado como Blob (gerado client-side ou server-side).
 *
 * @param {object} params
 * @param {string} params.templateId
 * @param {string} params.patientId
 * @param {object} params.dadosPreenchidos - snapshot dos dados usados
 * @param {Blob}   params.pdfBlob - PDF gerado (opcional — pode ser null se so print)
 * @param {string} [params.editingDocId] - se setado, re-edita doc existente (UPDATE)
 * @returns {object} registro criado/atualizado em document_generated
 */
export async function saveGeneratedDocument({ templateId, patientId, dadosPreenchidos, pdfBlob, templateNome, templateTipo, editingDocId = null }) {
    const ownerId = await getOwnerId();
    const tenantId = await getActiveTenantId(ownerId);

    let pdfPath = '';
    const timestamp = Date.now();
    const safeNome = (templateNome || 'documento')
        .normalize('NFD').replace(/[\u0300-\u036f]/g, '') // remove acentos
        .replace(/[^a-zA-Z0-9_-]/g, '_');
    const filename = `${safeNome}_${timestamp}.pdf`;

    // Se tiver um blob PDF, faz upload ao Storage
    if (pdfBlob) {
        pdfPath = `${tenantId}/${patientId}/${filename}`;

        const { error: upErr } = await supabase.storage
            .from(BUCKET)
            .upload(pdfPath, pdfBlob, { contentType: 'application/pdf' });

        if (upErr) throw upErr;
    }

    // ─── MODO EDIT (UPDATE in-place) ─────────────────────────
    // Re-edicao: preserva documents.id (e o audit trail), substitui o PDF
    // no Storage, atualiza metadados. Best-effort cleanup do PDF antigo.
    if (editingDocId) {
        const { data: oldDoc } = await tenantDb().from('documents')
            .select('bucket_path, storage_bucket')
            .eq('id', editingDocId)
            .single();

        const docPatch = {
            tipo_documento: mapTipoDocumento(templateTipo),
            descricao: `Gerado a partir do template: ${templateNome || 'documento'}`
        };
        if (pdfPath) {
            docPatch.bucket_path = pdfPath;
            docPatch.storage_bucket = BUCKET;
            docPatch.tamanho_bytes = pdfBlob?.size || null;
            docPatch.nome_original = filename.replace(/_/g, ' ').replace('.pdf', '') + '.pdf';
        }
        const { error: upDocErr } = await tenantDb().from('documents')
            .update(docPatch)
            .eq('id', editingDocId);
        if (upDocErr) throw upDocErr;

        // Atualiza document_generated. Pode nao existir (docs legados sem
        // linkage) — INSERT nesse caso, com documento_id apontando pro doc.
        const { data: existingGen } = await tenantDb().from('document_generated')
            .select('id')
            .eq('documento_id', editingDocId)
            .maybeSingle();

        let data;
        if (existingGen) {
            const genPatch = {
                template_id: templateId,
                dados_preenchidos: dadosPreenchidos || {}
            };
            if (pdfPath) genPatch.pdf_path = pdfPath;
            const { data: updated, error: upGenErr } = await tenantDb().from('document_generated')
                .update(genPatch)
                .eq('id', existingGen.id)
                .select('*')
                .single();
            if (upGenErr) throw upGenErr;
            data = updated;
        } else {
            const { data: inserted, error: insGenErr } = await tenantDb().from('document_generated')
                .insert({
                    template_id: templateId,
                    patient_id: patientId,
                    dados_preenchidos: dadosPreenchidos || {},
                    pdf_path: pdfPath,
                    storage_bucket: BUCKET,
                    gerado_por: ownerId,
                    documento_id: editingDocId
                })
                .select('*')
                .single();
            if (insGenErr) throw insGenErr;
            data = inserted;
        }

        // Cleanup do PDF antigo no Storage. Falha silenciosa — arquivo orfao
        // nao quebra nada, so ocupa espaco minimo.
        if (oldDoc?.bucket_path && oldDoc.bucket_path !== pdfPath && oldDoc.storage_bucket) {
            supabase.storage.from(oldDoc.storage_bucket).remove([oldDoc.bucket_path])
                .catch((e) => console.warn('[saveGeneratedDocument] cleanup antigo falhou:', e));
        }

        return data;
    }

    // ─── MODO CREATE (insert) ────────────────────────────────
    // Insere documents primeiro pra capturar o id e linkar em
    // document_generated via documento_id (FK).
    let documentoId = null;
    if (pdfPath) {
        const { data: newDoc, error: insDocErr } = await tenantDb().from('documents')
            .insert({
                owner_id: ownerId,
                patient_id: patientId,
                bucket_path: pdfPath,
                storage_bucket: BUCKET,
                nome_original: filename.replace(/_/g, ' ').replace('.pdf', '') + '.pdf',
                mime_type: 'application/pdf',
                tamanho_bytes: pdfBlob?.size || null,
                tipo_documento: mapTipoDocumento(templateTipo),
                descricao: `Gerado a partir do template: ${templateNome || 'documento'}`,
                tags: ['gerado'],
                visibilidade: 'privado',
                status_revisao: 'aprovado',
                uploaded_by: ownerId
            })
            .select('id')
            .single();
        if (insDocErr) throw insDocErr;
        documentoId = newDoc?.id || null;
    }

    // Registra em document_generated com o linkage documento_id preenchido
    const { data, error } = await tenantDb().from('document_generated')
        .insert({
            template_id: templateId,
            patient_id: patientId,
            dados_preenchidos: dadosPreenchidos || {},
            pdf_path: pdfPath,
            storage_bucket: BUCKET,
            gerado_por: ownerId,
            documento_id: documentoId
        })
        .select('*')
        .single();

    if (error) throw error;

    return data;
}

// ── Buscar generated existente pra modo edit ─────────────────

/**
 * Busca o registro document_generated vinculado a um documents.id.
 * Retorna template_id + dados_preenchidos pra pre-popular o dialog.
 * Null se nao houver linkage (docs uploaded direto, sem template).
 */
export async function loadGeneratedFromDocId(documentoId) {
    if (!documentoId) return null;
    const { data, error } = await tenantDb().from('document_generated')
        .select('id, template_id, dados_preenchidos, pdf_path, gerado_em')
        .eq('documento_id', documentoId)
        .maybeSingle();
    if (error) {
        console.error('[loadGeneratedFromDocId]', error);
        return null;
    }
    return data;
}

// ── Quick path: emitir recibo de sessão ─────────────────────
//
// Wrapper de alto nível que tudo numa chamada só:
//   1. busca template global 'recibo_pagamento'
//   2. carrega variáveis (paciente + sessão + terapeuta + clínica + valor + extenso)
//   3. gera PDF
//   4. salva no Storage + tabelas document_generated + documents
//   5. dispara download
//
// Usado pelo popover da agenda e painel financeiro do paciente.
// Pra fluxo customizado (escolher template, editar variáveis, etc),
// continue usando DocumentGenerateDialog → useDocumentGenerate.
//
export async function emitirReciboParaSessao(agendaEventoId, { patientId, valor, formaPagamento } = {}) {
    if (!agendaEventoId && !patientId) {
        throw new Error('Informe agendaEventoId ou patientId.');
    }

    // Resolve patient_id pela sessão se não veio
    let resolvedPatientId = patientId;
    if (!resolvedPatientId && agendaEventoId) {
        const { data, error } = await tenantDb().from('agenda_eventos')
            .select('paciente_id')
            .eq('id', agendaEventoId)
            .single();
        if (error) throw error;
        resolvedPatientId = data?.paciente_id;
        if (!resolvedPatientId) throw new Error('Sessão sem paciente vinculado.');
    }

    // Busca template global recibo_pagamento
    const { data: tpl, error: tplErr } = await tenantDb().from('document_templates')
        .select('*')
        .eq('tipo', 'recibo_pagamento')
        .eq('is_global', true)
        .eq('ativo', true)
        .limit(1)
        .single();
    if (tplErr) throw tplErr;
    if (!tpl) throw new Error('Template de recibo não encontrado.');

    // Variáveis (extras sobrescrevem session.valor / forma_pagamento)
    const extras = {};
    if (valor != null) extras.valor = valor;
    if (formaPagamento) extras.forma_pagamento = formaPagamento;

    const variables = await loadAllVariables(resolvedPatientId, agendaEventoId, extras);

    // Gera PDF
    const blob = await generatePdfBlob(tpl, variables);

    // Salva
    const saved = await saveGeneratedDocument({
        templateId: tpl.id,
        patientId: resolvedPatientId,
        dadosPreenchidos: variables,
        pdfBlob: blob,
        templateNome: tpl.nome_template,
        templateTipo: tpl.tipo
    });

    // Download client-side
    const a = document.createElement('a');
    a.href = URL.createObjectURL(blob);
    const dataStr = (variables.data_sessao || variables.data_atual || '').replace(/\//g, '-');
    a.download = `recibo_${(variables.paciente_nome || 'paciente').replace(/\s+/g, '_')}_${dataStr || 'sessao'}.pdf`;
    document.body.appendChild(a);
    a.click();
    document.body.removeChild(a);
    setTimeout(() => URL.revokeObjectURL(a.href), 5000);

    return { generated: saved, variables, blob };
}

/**
 * Lista documentos gerados de um paciente.
 */
export async function listGeneratedDocuments(patientId) {
    const ownerId = await getOwnerId();

    const { data, error } = await tenantDb().from('document_generated')
        .select('*, document_templates(nome_template, tipo)')
        .eq('gerado_por', ownerId)
        .eq('patient_id', patientId)
        .order('gerado_em', { ascending: false });

    if (error) throw error;
    return data || [];
}
