/*
|--------------------------------------------------------------------------
| Agência PSI
|--------------------------------------------------------------------------
| Criado e desenvolvido por Leonardo Nohama
|
| Arquivo: src/stores/conversationDrawerStore.js
|
| Store global do drawer de conversa. Permite abrir de qualquer página
| (ficha de paciente, lista, agenda, dashboard) sem navegar.
|--------------------------------------------------------------------------
*/
import { defineStore } from 'pinia';
import { supabase } from '@/lib/supabase/client';
import { tenantDb } from '@/lib/supabase/tenantClient';
import { useTenantStore } from '@/stores/tenantStore';

// Classifica o erro do envio em { message, hint, action } pra UI mostrar
// um banner persistente no chat (não só toast). Diferente do friendly antigo
// que retornava só string, esse devolve estrutura — assim o componente pode
// renderizar título + dica explicativa + botão de ação contextual.
//
// Inputs:
//   code             — string vinda de error.error / data.error
//   providedMessage  — string vinda de error.message / data.message
//   status           — HTTP status (error.context?.status) — captura 502/503/504
//
// O edge function retorna { ok: false, error: '<code>', message: '<human>' }.
//
// Exportada pra ser testada isoladamente — o resto do store nao precisa
// dela como API publica, mas eh deterministica e vale fixar o comportamento.
export function classifySendError(code, providedMessage, status) {
    const c = String(code || '').toLowerCase();
    const hasMsg = !!providedMessage;
    const httpStatus = Number(status) || null;

    // ── 5xx (gateway/server offline): caso típico de Evolution/Twilio fora ──
    // Quando a edge function de envio retorna 502/503/504, normalmente o
    // provedor (Evolution self-hosted ou Twilio) está fora ou inalcançável.
    if (httpStatus === 502 || httpStatus === 503 || httpStatus === 504) {
        return {
            code: c || `http_${httpStatus}`,
            status: httpStatus,
            message: 'Servidor de WhatsApp temporariamente fora do ar.',
            hint: 'Os envios costumam voltar em poucos minutos. Se você ainda não configurou ou contratou o serviço, verifique abaixo.',
            action: { label: 'Configurar WhatsApp', url: '/configuracoes/whatsapp' },
            secondaryAction: { label: 'Comprar créditos', url: '/configuracoes/creditos-whatsapp' }
        };
    }

    if (c === 'insufficient_credits') {
        return {
            code: c, status: httpStatus,
            message: 'Saldo de créditos insuficiente.',
            hint: 'Você precisa de créditos pra enviar mensagens pelo WhatsApp Oficial.',
            action: { label: 'Comprar créditos', url: '/configuracoes/creditos-whatsapp' }
        };
    }

    if (c.includes('canal whatsapp nao configurado') || c.includes('canal whatsapp inativo')) {
        return {
            code: c, status: httpStatus,
            message: 'Nenhum canal WhatsApp ativo.',
            hint: 'Você ainda não configurou um número. Conecte um WhatsApp Pessoal (gratuito) ou contrate o WhatsApp Oficial.',
            action: { label: 'Configurar agora', url: '/configuracoes/whatsapp' }
        };
    }

    if (c.includes('credenciais evolution incompletas')) {
        return {
            code: c, status: httpStatus,
            message: 'WhatsApp Pessoal está com credenciais incompletas.',
            hint: 'Termine a conexão do seu número (escaneie o QR Code novamente, se necessário).',
            action: { label: 'Configurações WhatsApp', url: '/configuracoes/whatsapp' }
        };
    }

    if (c.includes('twilio credenciais incompletas')) {
        return {
            code: c, status: httpStatus,
            message: 'WhatsApp Oficial está com credenciais incompletas.',
            hint: 'Contate o suporte pra finalizar a configuração da sua conta Twilio.',
            action: null
        };
    }

    if (c.startsWith('evolution retornou')) {
        return {
            code: c, status: httpStatus,
            message: 'WhatsApp Pessoal não respondeu.',
            hint: 'Verifique se o celular conectado está com internet e a sessão do WhatsApp ativa.',
            action: { label: 'Ver status', url: '/configuracoes/whatsapp' }
        };
    }

    if (c.startsWith('twilio_send_failed')) {
        return {
            code: c, status: httpStatus,
            message: 'WhatsApp Oficial recusou o envio.',
            hint: 'Verifique se o canal está conectado e se o número de destino aceita mensagens template fora da janela de 24h.',
            action: { label: 'Configurações WhatsApp', url: '/configuracoes/whatsapp' }
        };
    }

    if (c === 'auth') {
        return {
            code: c, status: httpStatus,
            message: 'Sua sessão expirou.',
            hint: 'Faça login novamente pra continuar.',
            action: null
        };
    }

    if (c === 'forbidden') {
        return {
            code: c, status: httpStatus,
            message: 'Você não tem permissão pra enviar por este canal.',
            hint: 'Verifique seu plano ou contate o admin do tenant.',
            action: null
        };
    }

    if (c === 'edge function returned a non-2xx status code') {
        return {
            code: c, status: httpStatus,
            message: 'O servidor recusou o envio.',
            hint: 'Tente novamente em instantes. Se persistir, verifique configuração ou contate o suporte.',
            action: { label: 'Configurar WhatsApp', url: '/configuracoes/whatsapp' }
        };
    }

    // Fallback genérico — usa providedMessage se houver, senão o código
    return {
        code: c || 'unknown',
        status: httpStatus,
        message: hasMsg ? providedMessage : 'Não foi possível enviar a mensagem.',
        hint: 'Tente novamente em alguns instantes. Se persistir, verifique sua conexão e a configuração do WhatsApp.',
        action: { label: 'Configurações WhatsApp', url: '/configuracoes/whatsapp' }
    };
}

export const useConversationDrawerStore = defineStore('conversationDrawer', {
    state: () => ({
        isOpen: false,
        thread: null, // { patient_id, patient_name, contact_number, channel, kanban_status, ... }
        messages: [],
        loading: false,
        sending: false,
        // Último erro de envio (estruturado, com hint + action) — fica no chat
        // como banner persistente até user enviar com sucesso ou dispensar.
        lastSendError: null, // { code, status, message, hint, action: { label, url }, secondaryAction? }
        error: null,
        _realtimeChannel: null,
        // cache compartilhado
        templates: [],
        templatesLoaded: false,
        templatesLoading: false,
        tenantName: ''
    }),

    getters: {
        threadKey(state) {
            if (!state.thread) return null;
            return state.thread.patient_id || `anon:${state.thread.contact_number || 'unknown'}`;
        }
    },

    actions: {
        // ── Abertura ───────────────────────────────────────────────
        async openForThread(thread) {
            if (!thread) return;
            this.thread = { ...thread };
            this.isOpen = true;
            // Erros de envio são por-conversa — não vazam pra próxima
            this.lastSendError = null;
            await this.loadMessages();
            this._ensureTenantName();
            this._subscribeRealtime();
            // Marca inbound como lidas
            if ((thread.unread_count || 0) > 0) {
                await this.markRead();
            }
        },

        async openForPatient(patientId) {
            if (!patientId) return;
            const tenantStore = useTenantStore();
            const tenantId = tenantStore.activeTenantId;

            // Procura thread existente pelo paciente
            const { data: existing } = await tenantDb().from('conversation_threads')
                .select('*')
                .eq('patient_id', patientId)
                .limit(1)
                .maybeSingle();

            if (existing) {
                return this.openForThread(existing);
            }

            // Não tem thread — cria stub com dados do paciente
            const { data: pat } = await tenantDb().from('patients')
                .select('id, nome_completo, telefone')
                .eq('id', patientId)
                .maybeSingle();

            if (!pat) {
                this.error = new Error('Paciente não encontrado');
                return;
            }
            if (!pat.telefone) {
                this.error = new Error('Paciente sem telefone cadastrado');
                return;
            }

            const stub = {
                thread_key: pat.id,
                tenant_id: tenantId,
                patient_id: pat.id,
                patient_name: pat.nome_completo,
                contact_number: String(pat.telefone).replace(/\D/g, ''),
                channel: 'whatsapp',
                message_count: 0,
                unread_count: 0,
                kanban_status: 'awaiting_us',
                last_message_at: null,
                last_message_body: null,
                last_message_direction: null
            };
            return this.openForThread(stub);
        },

        close() {
            this.isOpen = false;
            this._unsubscribeRealtime();
            // preserva thread por um momento pra transição, mas limpa messages
            setTimeout(() => {
                if (!this.isOpen) {
                    this.thread = null;
                    this.messages = [];
                }
            }, 300);
        },

        // ── Mensagens da thread ────────────────────────────────────
        async loadMessages() {
            if (!this.thread) return;
            const tenantStore = useTenantStore();
            this.loading = true;
            try {
                let q = tenantDb().from('conversation_messages')
                    .select('*')
                    .order('created_at', { ascending: true })
                    .limit(500);

                if (this.thread.patient_id) {
                    q = q.eq('patient_id', this.thread.patient_id);
                } else {
                    q = q.or(`from_number.eq.${this.thread.contact_number},to_number.eq.${this.thread.contact_number}`).is('patient_id', null);
                }

                const { data, error: qErr } = await q;
                if (qErr) throw qErr;
                this.messages = data ?? [];
            } catch (err) {
                this.error = err;
            } finally {
                this.loading = false;
            }
        },

        messageBelongsToCurrentThread(msg) {
            if (!msg || !this.thread) return false;
            if (this.thread.patient_id) return msg.patient_id === this.thread.patient_id;
            if (msg.patient_id) return false;
            return (
                msg.from_number === this.thread.contact_number ||
                msg.to_number === this.thread.contact_number
            );
        },

        _subscribeRealtime() {
            const tenantStore = useTenantStore();
            const tenantId = tenantStore.activeTenantId;
            const tenantSchema = tenantStore.activeTenantSchema;
            if (!tenantId || !tenantSchema) return;
            this._unsubscribeRealtime();

            this._realtimeChannel = supabase
                .channel(`conv_drawer_${tenantId}_${Date.now()}`)
                .on(
                    'postgres_changes',
                    { event: 'INSERT', schema: tenantSchema, table: 'conversation_messages' },
                    (payload) => {
                        const msg = payload.new;
                        if (this.messageBelongsToCurrentThread(msg)) {
                            const exists = this.messages.some((m) => m.id === msg.id);
                            if (!exists) this.messages.push(msg);
                        }
                    }
                )
                .on(
                    'postgres_changes',
                    { event: 'UPDATE', schema: tenantSchema, table: 'conversation_messages' },
                    (payload) => {
                        const msg = payload.new;
                        if (this.messageBelongsToCurrentThread(msg)) {
                            const idx = this.messages.findIndex((m) => m.id === msg.id);
                            if (idx >= 0) this.messages[idx] = msg;
                        }
                    }
                )
                .subscribe();
        },

        _unsubscribeRealtime() {
            if (this._realtimeChannel) {
                supabase.removeChannel(this._realtimeChannel);
                this._realtimeChannel = null;
            }
        },

        // ── Ações ──────────────────────────────────────────────────
        async sendMessage(text) {
            const cleanText = String(text || '').trim();
            if (!cleanText || this.sending) return { ok: false, error: 'Mensagem vazia' };
            if (!this.thread?.contact_number) {
                const cls = { code: 'no_contact', status: null, message: 'Conversa sem número de contato', hint: 'Vincule um paciente com telefone à conversa antes de enviar.', action: null };
                this.lastSendError = cls;
                return { ok: false, error: cls.message, classification: cls };
            }

            const tenantStore = useTenantStore();
            this.sending = true;
            try {
                const { data, error } = await supabase.functions.invoke('send-whatsapp-message', {
                    body: {
                        tenant_id: tenantStore.activeTenantId,
                        to_number: this.thread.contact_number,
                        body: cleanText,
                        patient_id: this.thread.patient_id || null
                    }
                });

                // Erro HTTP (não-2xx) — extrai status + body da resposta
                if (error) {
                    const status = error.context?.status || null;
                    let body = null;
                    try {
                        body = await error.context?.json?.();
                    } catch { /* noop — pode não ter body JSON em 502 */ }
                    const cls = classifySendError(body?.error, body?.message, status);
                    this.lastSendError = cls;
                    return { ok: false, error: cls.message, classification: cls };
                }
                if (!data?.ok) {
                    const cls = classifySendError(data?.error, data?.message, null);
                    this.lastSendError = cls;
                    return { ok: false, error: cls.message, classification: cls };
                }

                // Sucesso — limpa banner de erro anterior
                this.lastSendError = null;
                this.thread.kanban_status = 'awaiting_patient';
                return { ok: true, data };
            } catch (err) {
                // Provavelmente erro de rede (fetch falhou). Sem `error.context`.
                const cls = classifySendError(err?.message || String(err), null, null);
                this.lastSendError = cls;
                return { ok: false, error: cls.message, classification: cls };
            } finally {
                this.sending = false;
            }
        },

        dismissSendError() {
            this.lastSendError = null;
        },

        async setKanbanStatus(status) {
            if (!['urgent', 'awaiting_us', 'awaiting_patient', 'resolved'].includes(status)) return;
            if (!this.thread) return;
            const tenantStore = useTenantStore();
            const patch = { kanban_status: status };
            if (status === 'resolved') patch.resolved_at = new Date().toISOString();

            let q = tenantDb().from('conversation_messages').update(patch);
            if (this.thread.patient_id) q = q.eq('patient_id', this.thread.patient_id);
            else q = q.eq('from_number', this.thread.contact_number).is('patient_id', null);

            await q;
            this.thread.kanban_status = status;
        },

        async markRead() {
            if (!this.thread) return;
            const tenantStore = useTenantStore();
            const nowIso = new Date().toISOString();
            let q = tenantDb().from('conversation_messages')
                .update({ read_at: nowIso })
                .eq('direction', 'inbound')
                .is('read_at', null);
            if (this.thread.patient_id) q = q.eq('patient_id', this.thread.patient_id);
            else q = q.eq('from_number', this.thread.contact_number).is('patient_id', null);
            await q;
        },

        // ── Templates (cache global) ───────────────────────────────
        async loadTemplates({ force = false } = {}) {
            if ((this.templatesLoaded || this.templatesLoading) && !force) return;
            const tenantStore = useTenantStore();
            const tenantId = tenantStore.activeTenantId;
            if (!tenantId) return;

            this.templatesLoading = true;
            try {
                const { data: globals } = await tenantDb().from('notification_templates')
                    .select('id, key, body_text, variables, event_type')
                    .eq('channel', 'whatsapp')
                    .eq('is_default', true)
                    .eq('is_active', true)
                    .is('deleted_at', null);

                const { data: customs } = await tenantDb().from('notification_templates')
                    .select('id, key, body_text, variables, event_type')
                    .eq('channel', 'whatsapp')
                    .eq('is_default', false)
                    .eq('is_active', true)
                    .is('deleted_at', null);

                const customMap = new Map((customs || []).map((c) => [c.key, c]));
                const merged = (globals || []).map((g) => customMap.get(g.key) || g);
                for (const c of customs || []) {
                    if (!merged.find((m) => m.key === c.key)) merged.push(c);
                }
                this.templates = merged;
                this.templatesLoaded = true;
            } finally {
                this.templatesLoading = false;
            }
        },

        invalidateTemplates() {
            this.templatesLoaded = false;
        },

        async _ensureTenantName() {
            if (this.tenantName) return;
            const tenantStore = useTenantStore();
            const tenantId = tenantStore.activeTenantId;
            if (!tenantId) return;
            const { data } = await supabase.from('tenants').select('name').eq('id', tenantId).maybeSingle();
            this.tenantName = data?.name || '';
        },

        // ── Resolução de variáveis de template ─────────────────────
        // Retorna: { text, missing } — missing é lista de variáveis não resolvidas
        async resolveTemplate(tpl) {
            if (!tpl?.body_text) return { text: '', missing: [] };

            const tenantStore = useTenantStore();
            const tenantId = tenantStore.activeTenantId;

            // Contexto base (sempre disponível)
            const now = new Date();
            const ctx = {
                data: now.toLocaleDateString('pt-BR'),
                hora: now.toLocaleTimeString('pt-BR', { hour: '2-digit', minute: '2-digit' }),
                nome_paciente: this.thread?.patient_name || '',
                nome_clinica: this.tenantName || '',
                clinica: this.tenantName || '',
                // aliases curtos
                paciente: this.thread?.patient_name || '',
                nome: (this.thread?.patient_name || '').split(' ')[0] || '',
                primeiro_nome: (this.thread?.patient_name || '').split(' ')[0] || ''
            };

            // Enriquece com dados do DB (terapeuta + próxima sessão)
            try {
                const { data: authData } = await supabase.auth.getUser();
                const uid = authData?.user?.id;
                if (uid) {
                    const { data: profile } = await supabase.from('profiles').select('full_name, nickname').eq('id', uid).maybeSingle();
                    const name = profile?.full_name || profile?.nickname || '';
                    ctx.nome_terapeuta = name;
                    ctx.terapeuta = name;
                }

                if (this.thread?.patient_id && tenantId) {
                    const { data: sess } = await tenantDb().from('agenda_eventos')
                        .select('inicio_em, modalidade, price')
                        .eq('patient_id', this.thread.patient_id)
                        .gte('inicio_em', new Date().toISOString())
                        .neq('tipo', 'bloqueio')
                        .order('inicio_em', { ascending: true })
                        .limit(1)
                        .maybeSingle();
                    if (sess?.inicio_em) {
                        const d = new Date(sess.inicio_em);
                        ctx.data_sessao = d.toLocaleDateString('pt-BR', { day: '2-digit', month: 'long', year: 'numeric' });
                        ctx.hora_sessao = d.toLocaleTimeString('pt-BR', { hour: '2-digit', minute: '2-digit' });
                        ctx.modalidade = sess.modalidade === 'online' ? 'Online' : 'Presencial';
                        if (sess.price) {
                            ctx.valor_sessao = Number(sess.price).toLocaleString('pt-BR', { style: 'currency', currency: 'BRL' });
                        }
                    }
                }
            } catch (err) {
                // contexto opcional — segue sem
            }

            // Substitui {{var}} (Mustache) e também {var} (legado) case-insensitive
            const missing = [];
            const text = String(tpl.body_text).replace(/\{\{?\s*([a-zA-Z_][a-zA-Z0-9_]*)\s*\}?\}/g, (match, varName) => {
                const key = varName.toLowerCase();
                if (key in ctx && ctx[key] != null && ctx[key] !== '') {
                    return ctx[key];
                }
                if (!missing.includes(varName)) missing.push(varName);
                return match; // preserva placeholder pro user editar manualmente
            });

            return { text, missing };
        }
    }
});
