<!--
|--------------------------------------------------------------------------
| Agência PSI
|--------------------------------------------------------------------------
| Criado e desenvolvido por Leonardo Nohama
|
| Tecnologia aplicada à escuta.
| Estrutura para o cuidado.
|
| Arquivo: src/views/pages/saas/SaasSubscriptionHealthPage.vue
| Data: 2026
| Local: São Carlos/SP — Brasil
|--------------------------------------------------------------------------
| © 2026 — Todos os direitos reservados
|--------------------------------------------------------------------------
-->
<script setup>
import { ref, computed, onMounted, onBeforeUnmount } from 'vue';
import { useRouter } from 'vue-router';
import { supabase } from '@/lib/supabase/client';
import { useToast } from 'primevue/usetoast';
import { useConfirm } from 'primevue/useconfirm';

import TabView from 'primevue/tabview';
import TabPanel from 'primevue/tabpanel';
import Message from 'primevue/message';

const router = useRouter();
const toast = useToast();
const confirm = useConfirm();

const loading = ref(false);
const fixing = ref(false);
const fixingOwner = ref(null);
const isFetching = ref(false);

const activeTab = ref(0); // 0 = terapeutas, 1 = clínicas

const q = ref('');

const personalRows = ref([]); // v_subscription_feature_mismatch
const clinicRows = ref([]); // v_tenant_feature_exceptions

// -----------------------------
// Labels / Severities
// -----------------------------
function severityForMismatch(t) {
    if (t === 'missing_entitlement') return 'danger';
    if (t === 'unexpected_entitlement') return 'warning';
    return 'secondary';
}

function labelForMismatch(t) {
    if (t === 'missing_entitlement') return 'Faltando';
    if (t === 'unexpected_entitlement') return 'Inesperado';
    return t || '—';
}

function helpForMismatch(t) {
    if (t === 'missing_entitlement') return 'O plano exige, mas não está ativo';
    if (t === 'unexpected_entitlement') return 'Está ativo, mas não consta no plano';
    return '';
}

function severityForException() {
    return 'info';
}

function labelForException(t) {
    if (t === 'commercial_exception') return 'Exceção comercial';
    return t || 'Exceção';
}

function helpForException() {
    return 'Feature liberada manualmente fora do plano (exceção controlada)';
}

// -----------------------------
// Computeds (Terapeutas)
// -----------------------------
const totalPersonal = computed(() => personalRows.value.length);
const totalPersonalMissing = computed(() => personalRows.value.filter((r) => r.mismatch_type === 'missing_entitlement').length);
const totalPersonalUnexpected = computed(() => personalRows.value.filter((r) => r.mismatch_type === 'unexpected_entitlement').length);
const totalPersonalWithoutOwner = computed(() => personalRows.value.filter((r) => !r.owner_id).length);

// -----------------------------
// Computeds (Clínicas)
// -----------------------------
const totalClinic = computed(() => clinicRows.value.length);

// -----------------------------
// Search filtering
// -----------------------------
const filteredPersonal = computed(() => {
    const term = String(q.value || '')
        .trim()
        .toLowerCase();
    if (!term) return personalRows.value;

    return personalRows.value.filter(
        (r) =>
            String(r.owner_id || '')
                .toLowerCase()
                .includes(term) ||
            String(r.feature_key || '')
                .toLowerCase()
                .includes(term) ||
            String(r.mismatch_type || '')
                .toLowerCase()
                .includes(term)
    );
});

const filteredClinic = computed(() => {
    const term = String(q.value || '')
        .trim()
        .toLowerCase();
    if (!term) return clinicRows.value;

    return clinicRows.value.filter(
        (r) =>
            String(r.tenant_id || '')
                .toLowerCase()
                .includes(term) ||
            String(r.tenant_name || '')
                .toLowerCase()
                .includes(term) ||
            String(r.plan_key || '')
                .toLowerCase()
                .includes(term) ||
            String(r.feature_key || '')
                .toLowerCase()
                .includes(term) ||
            String(r.exception_type || '')
                .toLowerCase()
                .includes(term)
    );
});

// -----------------------------
// Fetch
// -----------------------------
async function fetchPersonal() {
    const { data, error } = await supabase.from('v_subscription_feature_mismatch').select('*');

    if (error) throw error;

    personalRows.value = (data || []).map((r) => ({
        ...r,
        __rowKey: `${r.owner_id || 'no_owner'}|${r.feature_key || 'no_feature'}|${r.mismatch_type || 'no_type'}`
    }));
}

async function fetchClinic() {
    const { data, error } = await supabase.from('v_tenant_feature_exceptions').select('*');

    if (error) throw error;

    clinicRows.value = (data || []).map((r) => ({
        ...r,
        __rowKey: `${r.tenant_id || 'no_tenant'}|${r.feature_key || 'no_feature'}|${r.exception_type || 'no_type'}`
    }));
}

async function fetchAll() {
    if (isFetching.value) return;
    isFetching.value = true;
    loading.value = true;

    try {
        await Promise.all([fetchPersonal(), fetchClinic()]);
    } catch (e) {
        toast.add({ severity: 'error', summary: 'Erro', detail: e?.message || String(e), life: 5000 });
    } finally {
        loading.value = false;
        isFetching.value = false;
    }
}

async function reloadActiveTab() {
    if (isFetching.value) return;
    isFetching.value = true;
    loading.value = true;

    try {
        if (activeTab.value === 0) await fetchPersonal();
        else await fetchClinic();
    } catch (e) {
        toast.add({ severity: 'error', summary: 'Erro', detail: e?.message || String(e), life: 5000 });
    } finally {
        loading.value = false;
        isFetching.value = false;
    }
}

// -----------------------------
// Navigation helpers
// -----------------------------
async function inferOwnerKey(ownerId) {
    if (!ownerId) return null;

    try {
        const { data, error } = await supabase.from('tenants').select('id').eq('id', ownerId).maybeSingle();

        if (!error && data?.id) return `clinic:${ownerId}`;
    } catch (_) {}

    try {
        const { data, error } = await supabase.from('tenant_members').select('tenant_id').eq('tenant_id', ownerId).limit(1);

        if (!error && Array.isArray(data) && data.length) return `clinic:${ownerId}`;
    } catch (_) {}

    return `therapist:${ownerId}`;
}

async function openOwnerSubscriptions(ownerId) {
    if (!ownerId) {
        toast.add({
            severity: 'warn',
            summary: 'Owner ausente',
            detail: 'Esta linha está sem owner_id. Verifique dados inconsistentes.',
            life: 4200
        });
        return;
    }

    const key = await inferOwnerKey(ownerId);
    router.push({ path: '/saas/subscriptions', query: { q: key || ownerId } });
}

function openClinicSubscriptions(tenantId) {
    if (!tenantId) return;
    router.push({ path: '/saas/subscriptions', query: { q: `clinic:${tenantId}` } });
}

// -----------------------------
// Fix (Terapeutas)
// -----------------------------
function askFixOwner(ownerId) {
    if (!ownerId) {
        toast.add({
            severity: 'warn',
            summary: 'Owner ausente',
            detail: 'Não é possível corrigir um owner vazio.',
            life: 4500
        });
        return;
    }

    confirm.require({
        header: 'Confirmar correção',
        message: `Reconstruir entitlements para este owner?\n\n${ownerId}\n\nIsso recalcula os recursos ativos com base no plano atual.`,
        icon: 'pi pi-exclamation-triangle',
        acceptClass: 'p-button-danger',
        accept: () => fixOwner(ownerId)
    });
}

async function fixOwner(ownerId) {
    if (!ownerId) return;

    fixing.value = true;
    fixingOwner.value = ownerId;

    try {
        const { error } = await supabase.rpc('rebuild_owner_entitlements', {
            p_owner_id: ownerId
        });
        if (error) throw error;

        toast.add({
            severity: 'success',
            summary: 'Corrigido',
            detail: 'Entitlements reconstruídos para este owner.',
            life: 2500
        });

        await fetchPersonal();
    } catch (e) {
        toast.add({
            severity: 'error',
            summary: 'Erro ao corrigir owner',
            detail: e?.message || String(e),
            life: 5200
        });
    } finally {
        fixing.value = false;
        fixingOwner.value = null;
    }
}

function askFixAll() {
    if (!totalPersonal.value) return;

    confirm.require({
        header: 'Confirmar correção geral',
        message: `Reconstruir entitlements para TODOS os owners com divergência?\n\nTotal: ${totalPersonal.value}\n\nObs.: linhas sem owner_id indicam dado inconsistente.`,
        icon: 'pi pi-exclamation-triangle',
        acceptClass: 'p-button-danger',
        accept: () => fixAll()
    });
}

async function fixAll() {
    fixing.value = true;
    try {
        const { error } = await supabase.rpc('fix_all_subscription_mismatches');
        if (error) throw error;

        toast.add({
            severity: 'success',
            summary: 'Correção aplicada',
            detail: 'Entitlements reconstruídos para todos os owners com divergência.',
            life: 3000
        });

        await fetchPersonal();
    } catch (e) {
        toast.add({
            severity: 'error',
            summary: 'Erro ao corrigir tudo',
            detail: e?.message || String(e),
            life: 5200
        });
    } finally {
        fixing.value = false;
    }
}

// -----------------------------
// Exceptions (Clínicas)
// -----------------------------
function askRemoveException(tenantId, featureKey) {
    if (!tenantId || !featureKey) return;

    confirm.require({
        header: 'Remover exceção',
        message: `Desativar a exceção desta clínica?\n\nTenant: ${tenantId}\nFeature: ${featureKey}\n\nIsso desliga a liberação manual fora do plano.`,
        icon: 'pi pi-exclamation-triangle',
        acceptClass: 'p-button-danger',
        accept: () => removeException(tenantId, featureKey)
    });
}

async function removeException(tenantId, featureKey) {
    fixing.value = true;

    try {
        const { error } = await supabase.rpc('set_tenant_feature_exception', {
            p_tenant_id: tenantId,
            p_feature_key: featureKey,
            p_enabled: false,
            p_reason: 'Remoção via Saúde das Assinaturas'
        });
        if (error) throw error;

        toast.add({
            severity: 'success',
            summary: 'Exceção removida',
            detail: 'A liberação manual foi desativada para esta feature.',
            life: 2800
        });

        await fetchClinic();
    } catch (e) {
        toast.add({
            severity: 'error',
            summary: 'Erro ao remover exceção',
            detail: e?.message || String(e),
            life: 5200
        });
    } finally {
        fixing.value = false;
    }
}

// -------------------------
// Hero sticky
// -------------------------
const heroRef = ref(null);
const sentinelRef = ref(null);
const heroStuck = ref(false);
let heroObserver = null;
const mobileMenuRef = ref(null);

const heroMenuItems = computed(() => [
    {
        label: 'Recarregar aba',
        icon: 'pi pi-refresh',
        command: reloadActiveTab,
        disabled: loading.value || fixing.value
    },
    {
        label: 'Recarregar tudo',
        icon: 'pi pi-sync',
        command: fetchAll,
        disabled: loading.value || fixing.value
    },
    ...(activeTab.value === 0 && totalPersonal.value > 0
        ? [
              {
                  label: 'Corrigir tudo (terapeutas)',
                  icon: 'pi pi-wrench',
                  command: askFixAll,
                  disabled: loading.value || fixing.value
              }
          ]
        : [])
]);

onMounted(() => {
    fetchAll();

    if (sentinelRef.value) {
        heroObserver = new IntersectionObserver(
            ([entry]) => {
                heroStuck.value = !entry.isIntersecting;
            },
            { rootMargin: `${document.querySelector('.l2-main') ? '0px' : '-56px'} 0px 0px 0px`, threshold: 0 }
        );
        heroObserver.observe(sentinelRef.value);
    }
});

onBeforeUnmount(() => {
    heroObserver?.disconnect();
});
</script>

<template>
    <ConfirmDialog />

    <div class="flex flex-col gap-4 p-4">
        <!-- Header -->
        <div class="cfg-subheader">
            <div class="cfg-subheader__icon"><i class="pi pi-heart" /></div>
            <div class="min-w-0 flex-1">
                <div class="cfg-subheader__title">Saúde das Assinaturas</div>
                <div class="cfg-subheader__sub">Terapeutas: divergências entre plano e entitlements. Clínicas: exceções comerciais.</div>
            </div>
            <div class="hidden xl:flex items-center gap-2 shrink-0">
                <Button label="Recarregar" icon="pi pi-refresh" severity="secondary" outlined size="small" :loading="loading" :disabled="fixing" @click="reloadActiveTab" />
                <Button label="Recarregar tudo" icon="pi pi-sync" severity="secondary" outlined size="small" :loading="loading" :disabled="fixing" @click="fetchAll" />
                <Button v-if="activeTab === 0 && totalPersonal > 0" label="Corrigir tudo (terapeutas)" icon="pi pi-wrench" severity="danger" size="small" :loading="fixing" :disabled="loading" @click="askFixAll" />
            </div>
            <div class="flex xl:hidden shrink-0">
                <Button label="Ações" icon="pi pi-ellipsis-v" severity="warn" size="small" @click="(e) => mobileMenuRef.toggle(e)" />
                <Menu ref="mobileMenuRef" :model="heroMenuItems" popup />
            </div>
        </div>
        <!-- busca -->
        <div>
            <FloatLabel variant="on" class="w-full">
                <IconField class="w-full">
                    <InputIcon class="pi pi-search" />
                    <InputText v-model="q" id="health_search" class="w-full pr-10" variant="filled" :disabled="loading || fixing" autocomplete="off" />
                </IconField>
                <label for="health_search">Buscar...</label>
            </FloatLabel>
        </div>

        <TabView v-model:activeIndex="activeTab">
            <!-- ===================================================== -->
            <!-- Terapeutas (Personal) -->
            <!-- ===================================================== -->
            <TabPanel header="Terapeutas (Pessoal)">
                <div class="rounded-md border border-[var(--surface-border)] bg-[var(--surface-card)] p-3 mb-4">
                    <div class="flex flex-wrap gap-2 items-center justify-content-between">
                        <div class="flex gap-2 items-center flex-wrap">
                            <Tag :value="`Divergências: ${totalPersonal}`" severity="secondary" />
                            <Tag :value="`Faltando: ${totalPersonalMissing}`" severity="danger" />
                            <Tag :value="`Inesperado: ${totalPersonalUnexpected}`" severity="warning" />
                            <Tag v-if="totalPersonalWithoutOwner > 0" :value="`Sem owner: ${totalPersonalWithoutOwner}`" severity="warn" />
                        </div>

                        <div class="text-[1rem] text-[var(--text-color-secondary)]"><span class="font-medium">Faltando</span>: o plano exige, mas não está ativo · <span class="font-medium">Inesperado</span>: está ativo sem constar no plano</div>
                    </div>
                </div>

                <DataTable :value="filteredPersonal" dataKey="__rowKey" :loading="loading" stripedRows responsiveLayout="scroll" sortField="owner_id" :sortOrder="1">
                    <Column header="Owner (User)" style="min-width: 22rem">
                        <template #body="{ data }">
                            <div class="flex items-center justify-between gap-2">
                                <span class="font-medium">
                                    {{ data.owner_id || '—' }}
                                </span>
                                <Tag v-if="!data.owner_id" value="Sem owner" severity="warn" rounded />
                            </div>
                        </template>
                    </Column>

                    <Column header="Recurso" style="min-width: 18rem">
                        <template #body="{ data }">
                            <div class="flex flex-col">
                                <span class="font-medium">{{ data.feature_key }}</span>
                                <div class="text-[1rem] text-[var(--text-color-secondary)]">
                                    {{ helpForMismatch(data.mismatch_type) || '—' }}
                                </div>
                            </div>
                        </template>
                    </Column>

                    <Column header="Tipo" style="width: 12rem">
                        <template #body="{ data }">
                            <Tag :severity="severityForMismatch(data.mismatch_type)" :value="labelForMismatch(data.mismatch_type)" />
                        </template>
                    </Column>

                    <Column header="Ações" style="min-width: 20rem">
                        <template #body="{ data }">
                            <div class="flex gap-2 flex-wrap">
                                <Button
                                    icon="pi pi-external-link"
                                    severity="secondary"
                                    outlined
                                    v-tooltip.top="data.owner_id ? 'Abrir assinaturas deste owner (com filtro)' : 'Owner ausente'"
                                    :disabled="loading || fixing || !data.owner_id"
                                    @click="openOwnerSubscriptions(data.owner_id)"
                                />

                                <Button
                                    label="Corrigir owner"
                                    icon="pi pi-wrench"
                                    severity="danger"
                                    outlined
                                    v-tooltip.top="data.owner_id ? 'Reconstruir entitlements deste owner' : 'Owner ausente'"
                                    :loading="fixing && fixingOwner === data.owner_id"
                                    :disabled="loading || !data.owner_id || (fixing && fixingOwner !== data.owner_id)"
                                    @click="askFixOwner(data.owner_id)"
                                />
                            </div>
                        </template>
                    </Column>
                </DataTable>

                <Divider class="my-5" />

                <Message severity="info" class="mt-4">
                    <div class="text-[1rem] line-height-3">
                        <div class="mb-0">
                            <span class="font-semibold">Dica:</span>
                            Se você alterar o plano e o acesso não refletir imediatamente, esta aba exibirá as divergências entre o plano ativo e os entitlements atuais. A ação <span class="font-medium">Corrigir</span> reconstrói os entitlements do
                            owner com base no plano vigente e elimina inconsistências.
                        </div>
                    </div>
                </Message>
            </TabPanel>

            <!-- ===================================================== -->
            <!-- Clínicas (Tenant) -->
            <!-- ===================================================== -->
            <TabPanel header="Clínicas (Exceções)">
                <div class="rounded-md border border-[var(--surface-border)] bg-[var(--surface-card)] p-3 mb-4">
                    <div class="flex flex-wrap gap-2 items-center justify-content-between">
                        <div class="flex gap-2 items-center flex-wrap">
                            <Tag :value="`Exceções ativas: ${totalClinic}`" severity="info" />
                        </div>

                        <div class="text-[1rem] text-[var(--text-color-secondary)]">Exceções comerciais: features liberadas manualmente fora do plano. Útil para testes, suporte e acordos.</div>
                    </div>
                </div>

                <DataTable :value="filteredClinic" dataKey="__rowKey" :loading="loading" stripedRows responsiveLayout="scroll" sortField="tenant_id" :sortOrder="1">
                    <Column header="Clínica (Tenant)" style="min-width: 22rem">
                        <template #body="{ data }">
                            <div class="flex flex-col">
                                <span class="font-medium">{{ data.tenant_name || data.tenant_id }}</span>
                                <div class="text-[1rem] text-[var(--text-color-secondary)]">{{ data.tenant_name ? data.tenant_id : '—' }}</div>
                            </div>
                        </template>
                    </Column>

                    <Column header="Plano" style="width: 12rem">
                        <template #body="{ data }">
                            <Tag :value="data.plan_key || '—'" severity="secondary" />
                        </template>
                    </Column>

                    <Column header="Feature" style="min-width: 18rem">
                        <template #body="{ data }">
                            <div class="flex flex-col">
                                <span class="font-medium">{{ data.feature_key }}</span>
                                <div class="text-[1rem] text-[var(--text-color-secondary)]">
                                    {{ helpForException() }}
                                </div>
                            </div>
                        </template>
                    </Column>

                    <Column header="Tipo" style="width: 14rem">
                        <template #body="{ data }">
                            <Tag :severity="severityForException()" :value="labelForException(data.exception_type)" />
                        </template>
                    </Column>

                    <Column header="Ações" style="min-width: 22rem">
                        <template #body="{ data }">
                            <div class="flex gap-2 flex-wrap">
                                <Button
                                    icon="pi pi-external-link"
                                    severity="secondary"
                                    outlined
                                    v-tooltip.top="'Abrir assinaturas desta clínica (com filtro)'"
                                    :disabled="loading || fixing || !data.tenant_id"
                                    @click="openClinicSubscriptions(data.tenant_id)"
                                />

                                <Button
                                    label="Remover exceção"
                                    icon="pi pi-ban"
                                    severity="danger"
                                    outlined
                                    v-tooltip.top="'Desativar a liberação manual desta feature'"
                                    :disabled="loading || fixing || !data.tenant_id || !data.feature_key"
                                    @click="askRemoveException(data.tenant_id, data.feature_key)"
                                />
                            </div>
                        </template>
                    </Column>
                </DataTable>

                <Divider class="my-5" />

                <Message severity="info" class="mt-4">
                    <div class="text-[1rem] line-height-3">
                        <div class="mb-2">
                            <span class="font-semibold">Observação:</span>
                            Exceção é uma escolha de negócio. Quando ativa, pode liberar acesso mesmo que o plano não permita. Utilize <span class="font-medium">Remover exceção</span> quando a liberação deixar de fazer sentido.
                        </div>

                        <div class="mb-0">
                            <span class="font-semibold">Dica:</span>
                            Exceções comerciais liberam recursos fora do plano. Se o acesso não refletir como esperado, verifique se existe uma exceção ativa para esta clínica. A ação <span class="font-medium">Remover exceção</span> restaura o
                            comportamento estritamente definido pelo plano.
                        </div>
                    </div>
                </Message>
            </TabPanel>
        </TabView>
    </div>
</template>