-- =============================================================================
-- F1.3 — Schema-per-tenant: construção do schema _tenant_template
--
-- Clona a ESTRUTURA das 84 tabelas tenant-scoped (docs/F0_categorizacao.md §1)
-- a partir de public, SEM a coluna tenant_id:
--   * PK/UNIQUE compostos perdem tenant_id; PK/UNIQUE que eram SÓ (tenant_id)
--     viram coluna `singleton boolean` (tabela de config 1-linha-por-tenant)
--   * índices parciais WHERE tenant_id IS [NOT] NULL são fundidos/deduplicados
--   * sequences bigserial são localizadas no template (não compartilham public)
--   * FKs locais apontam pro template; FKs pra tabelas globais ficam em public/auth
--   * linhas-default do sistema (tenant_id IS NULL) viram SEED do template e
--     são copiadas pra cada tenant no clone
--   * 6 views adaptadas ficam registradas em _tenant_template._views com
--     placeholders __SCHEMA__ / __TENANT_ID__ (instanciadas no clone)
--
-- O template NUNCA é exposto no PostgREST nem recebe dados de tenant.
-- =============================================================================

BEGIN;

DROP SCHEMA IF EXISTS _tenant_template CASCADE;
CREATE SCHEMA _tenant_template;

-- Helper interno: remove tenant_id de uma definição de índice e simplifica
-- predicados parciais que testavam tenant_id.
CREATE FUNCTION _tenant_template._adapt_indexdef(p_def text)
RETURNS text
LANGUAGE plpgsql
IMMUTABLE
AS $$
DECLARE d text := p_def;
BEGIN
    -- coluna no início/meio/fim da lista
    d := regexp_replace(d, '\(tenant_id,\s*', '(', 'g');
    d := regexp_replace(d, ',\s*tenant_id\)', ')', 'g');
    d := regexp_replace(d, ',\s*tenant_id,', ',', 'g');
    -- predicados parciais
    d := replace(d, '(tenant_id IS NOT NULL) AND ', '');
    d := replace(d, ' AND (tenant_id IS NOT NULL)', '');
    d := replace(d, ' WHERE ((tenant_id IS NOT NULL))', '');
    d := replace(d, ' WHERE (tenant_id IS NOT NULL)', '');
    d := replace(d, '(tenant_id IS NULL) AND ', '');
    d := replace(d, ' AND (tenant_id IS NULL)', '');
    d := replace(d, ' WHERE ((tenant_id IS NULL))', '');
    d := replace(d, ' WHERE (tenant_id IS NULL)', '');
    RETURN d;
END;
$$;

DO $$
DECLARE
    tabs text[] := ARRAY[
        'agenda_bloqueios','agenda_configuracoes','agenda_eventos','agenda_online_slots',
        'agenda_regras_semanais','agenda_slots_bloqueados_semanais','agenda_slots_regras',
        'agendador_configuracoes','agendador_solicitacoes',
        'asaas_customers','asaas_payments',
        'billing_contracts',
        'clinical_note_templates','clinical_note_versions','clinical_notes',
        'commitment_services','commitment_time_logs',
        'company_profiles',
        'contact_email_types','contact_emails','contact_phones','contact_types',
        'conversation_assignments','conversation_autoreply_log','conversation_autoreply_settings',
        'conversation_bot_sessions','conversation_bots','conversation_messages','conversation_notes',
        'conversation_optout_keywords','conversation_optouts','conversation_sla_breaches',
        'conversation_sla_rules','conversation_tags','conversation_thread_tags',
        'determined_commitment_fields','determined_commitments',
        'document_access_logs','document_generated','document_share_links','document_signatures',
        'document_templates','documents',
        'email_layout_config','email_templates_tenant',
        'feriados',
        'financial_categories','financial_exceptions','financial_records',
        'insurance_plan_services','insurance_plans',
        'medicos',
        'notification_channels','notification_logs','notification_preferences','notification_queue',
        'notification_schedules','notification_templates','notifications',
        'patient_contacts','patient_discounts','patient_group_patient','patient_groups',
        'patient_intake_requests','patient_invite_attempts','patient_invites','patient_patient_tag',
        'patient_status_history','patient_support_contacts','patient_tags','patient_timeline','patients',
        'payment_settings','professional_pricing',
        'recurrence_exceptions','recurrence_rule_services','recurrence_rules',
        'services',
        'session_reminder_logs','session_reminder_settings',
        'therapist_payout_records','therapist_payouts',
        'twilio_subaccount_usage','whatsapp_connection_incidents'
    ];
    t text;
    r record;
    r2 record;
    v_def text;
    v_sig text;
    v_seq text;
    v_cols text;
    v_remaining text;
    v_n int;
    seen_sigs text[];
    pending text[] := ARRAY[]::text[];
    failed text[];
BEGIN
    PERFORM pg_catalog.set_config('search_path', 'pg_catalog', true);

    IF array_length(tabs, 1) <> 84 THEN
        RAISE EXCEPTION 'lista de tabelas tenant deveria ter 84, tem %', array_length(tabs, 1);
    END IF;

    ---------------------------------------------------------------------------
    -- PASS 1: clonar estrutura
    ---------------------------------------------------------------------------
    FOREACH t IN ARRAY tabs LOOP
        IF NOT EXISTS (SELECT 1 FROM information_schema.tables
                       WHERE table_schema = 'public' AND table_name = t AND table_type = 'BASE TABLE') THEN
            RAISE EXCEPTION 'tabela public.% não existe — lista F0 desatualizada', t;
        END IF;
        EXECUTE format('CREATE TABLE _tenant_template.%I (LIKE public.%I INCLUDING ALL)', t, t);
    END LOOP;
    RAISE NOTICE 'PASS 1 ok: % tabelas clonadas', array_length(tabs, 1);

    ---------------------------------------------------------------------------
    -- PASS 2: localizar sequences (defaults nextval apontando pra public)
    ---------------------------------------------------------------------------
    FOR r IN
        SELECT c.relname AS tab, a.attname AS col,
               pg_get_expr(d.adbin, d.adrelid) AS def
        FROM pg_attrdef d
        JOIN pg_class c ON c.oid = d.adrelid
        JOIN pg_attribute a ON a.attrelid = d.adrelid AND a.attnum = d.adnum
        WHERE c.relnamespace = '_tenant_template'::regnamespace
          AND pg_get_expr(d.adbin, d.adrelid) LIKE 'nextval(''public.%'
    LOOP
        v_seq := r.tab || '_' || r.col || '_seq';
        EXECUTE format('CREATE SEQUENCE _tenant_template.%I', v_seq);
        EXECUTE format('ALTER TABLE _tenant_template.%I ALTER COLUMN %I SET DEFAULT nextval(%L::regclass)',
                       r.tab, r.col, '_tenant_template.' || v_seq);
        EXECUTE format('ALTER SEQUENCE _tenant_template.%I OWNED BY _tenant_template.%I.%I',
                       v_seq, r.tab, r.col);
        RAISE NOTICE 'PASS 2: sequence local _tenant_template.% (%.%)', v_seq, r.tab, r.col;
    END LOOP;

    ---------------------------------------------------------------------------
    -- PASS 3: drop tenant_id + recriar constraints/índices sem a coluna
    ---------------------------------------------------------------------------
    FOREACH t IN ARRAY tabs LOOP
        IF NOT EXISTS (SELECT 1 FROM pg_attribute
                       WHERE attrelid = ('_tenant_template.' || quote_ident(t))::regclass
                         AND attname = 'tenant_id' AND NOT attisdropped) THEN
            CONTINUE; -- joins/children sem tenant_id (commitment_services etc.)
        END IF;

        -- 3a. capturar PK/UNIQUE que contêm tenant_id (no template)
        CREATE TEMP TABLE IF NOT EXISTS _f1_cons (tab text, conname text, contype char, remaining text) ON COMMIT DROP;
        DELETE FROM _f1_cons WHERE tab = t;
        INSERT INTO _f1_cons
        SELECT t, con.conname, con.contype,
               (SELECT string_agg(quote_ident(a.attname), ', ' ORDER BY k.ord)
                FROM unnest(con.conkey) WITH ORDINALITY k(attnum, ord)
                JOIN pg_attribute a ON a.attrelid = con.conrelid AND a.attnum = k.attnum
                WHERE a.attname <> 'tenant_id')
        FROM pg_constraint con
        WHERE con.conrelid = ('_tenant_template.' || quote_ident(t))::regclass
          AND con.contype IN ('p', 'u')
          AND EXISTS (SELECT 1 FROM unnest(con.conkey) k
                      JOIN pg_attribute a ON a.attrelid = con.conrelid AND a.attnum = k
                      WHERE a.attname = 'tenant_id');

        -- 3b. capturar índices "soltos" (não-constraint) que usam tenant_id
        CREATE TEMP TABLE IF NOT EXISTS _f1_idx (tab text, idxname text, def text) ON COMMIT DROP;
        DELETE FROM _f1_idx WHERE tab = t;
        INSERT INTO _f1_idx
        SELECT t, c2.relname, pg_get_indexdef(i.indexrelid)
        FROM pg_index i
        JOIN pg_class c2 ON c2.oid = i.indexrelid
        WHERE i.indrelid = ('_tenant_template.' || quote_ident(t))::regclass
          AND NOT EXISTS (SELECT 1 FROM pg_constraint cc WHERE cc.conindid = i.indexrelid)
          AND pg_get_indexdef(i.indexrelid) ~ '\mtenant_id\M';

        -- 3c. drop da coluna (leva junto constraints/índices que a usam)
        EXECUTE format('ALTER TABLE _tenant_template.%I DROP COLUMN tenant_id CASCADE', t);

        -- 3d. recriar PK/UNIQUE
        FOR r IN SELECT * FROM _f1_cons WHERE tab = t LOOP
            IF r.remaining IS NULL OR r.remaining = '' THEN
                -- era PK/UNIQUE exatamente (tenant_id): tabela 1-linha-por-tenant
                EXECUTE format('ALTER TABLE _tenant_template.%I ADD COLUMN singleton boolean NOT NULL DEFAULT true', t);
                EXECUTE format('ALTER TABLE _tenant_template.%I ADD CONSTRAINT %I CHECK (singleton = true)',
                               t, t || '_singleton_chk');
                IF r.contype = 'p' THEN
                    EXECUTE format('ALTER TABLE _tenant_template.%I ADD CONSTRAINT %I PRIMARY KEY (singleton)',
                                   t, r.conname);
                ELSE
                    EXECUTE format('ALTER TABLE _tenant_template.%I ADD CONSTRAINT %I UNIQUE (singleton)',
                                   t, r.conname);
                END IF;
                RAISE NOTICE 'PASS 3: %.% era (tenant_id) -> singleton (%)', t, r.conname,
                             CASE r.contype WHEN 'p' THEN 'PK' ELSE 'UNIQUE' END;
            ELSE
                EXECUTE format('ALTER TABLE _tenant_template.%I ADD CONSTRAINT %I %s (%s)',
                               t, r.conname,
                               CASE r.contype WHEN 'p' THEN 'PRIMARY KEY' ELSE 'UNIQUE' END,
                               r.remaining);
                RAISE NOTICE 'PASS 3: %.% recriado sem tenant_id -> (%)', t, r.conname, r.remaining;
            END IF;
        END LOOP;

        -- 3e. recriar índices soltos transformados (com dedupe)
        seen_sigs := ARRAY[]::text[];
        -- assinaturas dos índices que já existem na tabela (pós-recriação de constraints)
        FOR r2 IN
            SELECT regexp_replace(pg_get_indexdef(i.indexrelid),
                                  '^CREATE (UNIQUE )?INDEX [^ ]+ ON [^ ]+ ', '') AS sig
            FROM pg_index i
            WHERE i.indrelid = ('_tenant_template.' || quote_ident(t))::regclass
        LOOP
            seen_sigs := seen_sigs || r2.sig;
        END LOOP;

        FOR r IN SELECT * FROM _f1_idx WHERE tab = t LOOP
            -- índice cuja ÚNICA coluna era tenant_id: descartar
            IF r.def ~ '\(tenant_id\)( WHERE .*)?$' THEN
                RAISE NOTICE 'PASS 3: índice % descartado (era só tenant_id)', r.idxname;
                CONTINUE;
            END IF;
            v_def := _tenant_template._adapt_indexdef(r.def);
            v_sig := regexp_replace(v_def, '^CREATE (UNIQUE )?INDEX [^ ]+ ON [^ ]+ ', '');
            IF v_sig = ANY (seen_sigs) THEN
                RAISE NOTICE 'PASS 3: índice % deduplicado', r.idxname;
                CONTINUE;
            END IF;
            EXECUTE v_def;
            seen_sigs := seen_sigs || v_sig;
            RAISE NOTICE 'PASS 3: índice % recriado: %', r.idxname, v_sig;
        END LOOP;
    END LOOP;

    ---------------------------------------------------------------------------
    -- PASS 4: FKs (a partir das FKs reais de public)
    ---------------------------------------------------------------------------
    FOR r IN
        SELECT con.conname,
               cl.relname AS tab,
               ns2.nspname AS fschema,
               cl2.relname AS ftab,
               pg_get_constraintdef(con.oid) AS def,
               EXISTS (SELECT 1 FROM unnest(con.conkey) k
                       JOIN pg_attribute a ON a.attrelid = con.conrelid AND a.attnum = k
                       WHERE a.attname = 'tenant_id') AS uses_tenant_id
        FROM pg_constraint con
        JOIN pg_class cl ON cl.oid = con.conrelid
        JOIN pg_class cl2 ON cl2.oid = con.confrelid
        JOIN pg_namespace ns2 ON ns2.oid = cl2.relnamespace
        WHERE con.contype = 'f'
          AND cl.relnamespace = 'public'::regnamespace
          AND cl.relname = ANY (tabs)
        ORDER BY cl.relname, con.conname
    LOOP
        IF r.uses_tenant_id THEN
            RAISE NOTICE 'PASS 4: FK %.% descartada (coluna tenant_id removida)', r.tab, r.conname;
            CONTINUE;
        END IF;
        v_def := r.def;
        IF r.fschema = 'public' AND r.ftab = ANY (tabs) THEN
            -- alvo também é tenant-scoped -> referência intra-template
            v_def := regexp_replace(v_def,
                ' REFERENCES (public\.)?' || r.ftab || '\(',
                ' REFERENCES _tenant_template.' || r.ftab || '(');
        END IF;
        EXECUTE format('ALTER TABLE _tenant_template.%I ADD CONSTRAINT %I %s', r.tab, r.conname, v_def);
    END LOOP;
    RAISE NOTICE 'PASS 4 ok: FKs recriadas';

    ---------------------------------------------------------------------------
    -- PASS 5: seeds — linhas-default do sistema (tenant_id IS NULL em public)
    -- APENAS tabelas de lookup/template (whitelist): linhas operacionais órfãs
    -- com tenant_id NULL (intakes, convites, notifs) NÃO são defaults.
    -- Sem session_replication_role (postgres não é superuser no Supabase):
    -- resolve ordem de FK por tentativa-e-repetição em rounds.
    ---------------------------------------------------------------------------
    FOREACH t IN ARRAY ARRAY[
        'clinical_note_templates','contact_email_types','contact_types',
        'conversation_optout_keywords','conversation_tags','document_templates',
        'notification_templates','feriados'
    ] LOOP
        IF NOT EXISTS (SELECT 1 FROM information_schema.columns
                       WHERE table_schema = 'public' AND table_name = t AND column_name = 'tenant_id') THEN
            CONTINUE;
        END IF;
        EXECUTE format('SELECT count(*) FROM public.%I WHERE tenant_id IS NULL', t) INTO v_n;
        IF v_n = 0 THEN CONTINUE; END IF;
        pending := pending || t;
    END LOOP;

    WHILE coalesce(array_length(pending, 1), 0) > 0 LOOP
        failed := ARRAY[]::text[];
        FOREACH t IN ARRAY pending LOOP
            SELECT string_agg(quote_ident(c.column_name), ', ' ORDER BY c.ordinal_position)
              INTO v_cols
              FROM information_schema.columns c
             WHERE c.table_schema = '_tenant_template' AND c.table_name = t
               AND c.column_name <> 'singleton'
               AND EXISTS (SELECT 1 FROM information_schema.columns p
                           WHERE p.table_schema = 'public' AND p.table_name = t
                             AND p.column_name = c.column_name);
            BEGIN
                EXECUTE format('INSERT INTO _tenant_template.%I (%s) SELECT %s FROM public.%I WHERE tenant_id IS NULL',
                               t, v_cols, v_cols, t);
                RAISE NOTICE 'PASS 5: linhas-default semeadas em _tenant_template.%', t;
            EXCEPTION WHEN foreign_key_violation THEN
                failed := failed || t;
            END;
        END LOOP;
        IF array_length(failed, 1) = array_length(pending, 1) THEN
            RAISE EXCEPTION 'PASS 5: dependência circular/externa nos seeds: %', failed;
        END IF;
        pending := failed;
    END LOOP;
END $$;

-- =============================================================================
-- Metadados do template
-- =============================================================================

CREATE TABLE _tenant_template._meta (key text PRIMARY KEY, value jsonb NOT NULL);
INSERT INTO _tenant_template._meta VALUES
    ('template_version', '1'::jsonb),
    ('built_from', '"docs/F0_categorizacao.md"'::jsonb),
    ('triggers_pending', 'true'::jsonb); -- triggers de negócio só na F6

-- Tabelas que entram na publication supabase_realtime a cada clone
-- (espelha o estado atual da publication em public: conversation_messages, notifications)
CREATE TABLE _tenant_template._realtime_tables (table_name text PRIMARY KEY);
INSERT INTO _tenant_template._realtime_tables
SELECT tablename FROM pg_publication_tables
WHERE pubname = 'supabase_realtime' AND schemaname = 'public'
  AND tablename IN (
    'agenda_bloqueios','agenda_configuracoes','agenda_eventos','agenda_online_slots',
    'agenda_regras_semanais','agenda_slots_bloqueados_semanais','agenda_slots_regras',
    'agendador_configuracoes','agendador_solicitacoes','asaas_customers','asaas_payments',
    'billing_contracts','clinical_note_templates','clinical_note_versions','clinical_notes',
    'commitment_services','commitment_time_logs','company_profiles','contact_email_types',
    'contact_emails','contact_phones','contact_types','conversation_assignments',
    'conversation_autoreply_log','conversation_autoreply_settings','conversation_bot_sessions',
    'conversation_bots','conversation_messages','conversation_notes','conversation_optout_keywords',
    'conversation_optouts','conversation_sla_breaches','conversation_sla_rules','conversation_tags',
    'conversation_thread_tags','determined_commitment_fields','determined_commitments',
    'document_access_logs','document_generated','document_share_links','document_signatures',
    'document_templates','documents','email_layout_config','email_templates_tenant','feriados',
    'financial_categories','financial_exceptions','financial_records','insurance_plan_services',
    'insurance_plans','medicos','notification_channels','notification_logs','notification_preferences',
    'notification_queue','notification_schedules','notification_templates','notifications',
    'patient_contacts','patient_discounts','patient_group_patient','patient_groups',
    'patient_intake_requests','patient_invite_attempts','patient_invites','patient_patient_tag',
    'patient_status_history','patient_support_contacts','patient_tags','patient_timeline','patients',
    'payment_settings','professional_pricing','recurrence_exceptions','recurrence_rule_services',
    'recurrence_rules','services','session_reminder_logs','session_reminder_settings',
    'therapist_payout_records','therapist_payouts','twilio_subaccount_usage','whatsapp_connection_incidents'
  );

-- Views adaptadas (instanciadas pelo clone com __SCHEMA__ / __TENANT_ID__)
CREATE TABLE _tenant_template._views (
    view_name text PRIMARY KEY,
    position int NOT NULL,
    definition text NOT NULL
);

INSERT INTO _tenant_template._views VALUES
('conversation_threads', 1, $vw$
CREATE VIEW __SCHEMA__.conversation_threads WITH (security_invoker = true) AS
WITH base AS (
    SELECT cm.id, cm.patient_id, cm.channel, cm.body, cm.direction,
           cm.kanban_status, cm.read_at, cm.created_at,
           CASE WHEN cm.direction = 'inbound' THEN cm.from_number ELSE cm.to_number END AS contact_number,
           COALESCE(cm.patient_id::text,
                    'anon:' || COALESCE(CASE WHEN cm.direction = 'inbound' THEN cm.from_number ELSE cm.to_number END,
                                        'unknown')) AS thread_key
    FROM __SCHEMA__.conversation_messages cm
), latest AS (
    SELECT DISTINCT ON (base.thread_key)
           base.thread_key, base.patient_id, base.channel, base.contact_number,
           base.body AS last_message_body, base.direction AS last_message_direction,
           base.kanban_status, base.created_at AS last_message_at
    FROM base
    ORDER BY base.thread_key, base.created_at DESC
), counts AS (
    SELECT base.thread_key, count(*) AS message_count,
           count(*) FILTER (WHERE base.direction = 'inbound' AND base.read_at IS NULL) AS unread_count
    FROM base
    GROUP BY base.thread_key
)
SELECT '__TENANT_ID__'::uuid AS tenant_id,
       l.thread_key, l.patient_id, p.nome_completo AS patient_name,
       l.contact_number, l.channel, c.message_count, c.unread_count,
       l.last_message_at, l.last_message_body, l.last_message_direction,
       l.kanban_status, ca.assigned_to, ca.assigned_at
FROM latest l
JOIN counts c ON c.thread_key = l.thread_key
LEFT JOIN __SCHEMA__.patients p ON p.id = l.patient_id
LEFT JOIN __SCHEMA__.conversation_assignments ca ON ca.thread_key = l.thread_key
$vw$),
('audit_log_unified', 2, $vw$
CREATE VIEW __SCHEMA__.audit_log_unified WITH (security_invoker = true) AS
SELECT 'audit:' || al.id::text AS uid, al.tenant_id, al.user_id, al.entity_type, al.entity_id, al.action,
       CASE al.action
           WHEN 'insert' THEN 'Criou ' || al.entity_type
           WHEN 'update' THEN ('Alterou ' || al.entity_type) || COALESCE((' (' || array_to_string(al.changed_fields, ', ')) || ')', '')
           WHEN 'delete' THEN 'Excluiu ' || al.entity_type
       END AS description,
       al.created_at AS occurred_at, 'audit_logs' AS source,
       jsonb_build_object('old_values', al.old_values, 'new_values', al.new_values, 'changed_fields', al.changed_fields) AS details
FROM public.audit_logs al
WHERE al.tenant_id = '__TENANT_ID__'::uuid
UNION ALL
SELECT 'doc_access:' || dal.id::text, '__TENANT_ID__'::uuid, dal.user_id, 'document', dal.documento_id::text, dal.acao,
       CASE dal.acao
           WHEN 'visualizou' THEN 'Visualizou documento'
           WHEN 'baixou' THEN 'Baixou documento'
           WHEN 'imprimiu' THEN 'Imprimiu documento'
           WHEN 'compartilhou' THEN 'Compartilhou documento'
           WHEN 'assinou' THEN 'Assinou documento'
           ELSE dal.acao
       END,
       dal.acessado_em, 'document_access_logs',
       jsonb_build_object('ip', dal.ip::text, 'user_agent', dal.user_agent)
FROM __SCHEMA__.document_access_logs dal
UNION ALL
SELECT 'psh:' || psh.id::text, '__TENANT_ID__'::uuid, psh.alterado_por, 'patient_status', psh.patient_id::text, 'status_change',
       ((('Status do paciente: ' || COALESCE(psh.status_anterior, '—')) || ' → ') || psh.status_novo) || COALESCE((' (' || psh.motivo) || ')', ''),
       psh.alterado_em, 'patient_status_history',
       jsonb_build_object('status_anterior', psh.status_anterior, 'status_novo', psh.status_novo, 'motivo', psh.motivo,
                          'encaminhado_para', psh.encaminhado_para, 'data_saida', psh.data_saida)
FROM __SCHEMA__.patient_status_history psh
UNION ALL
SELECT 'notif:' || nl.id::text, '__TENANT_ID__'::uuid, nl.owner_id, 'notification', nl.patient_id::text, nl.status,
       ((('Notificação ' || nl.channel) || ' ') || nl.status) || COALESCE(' para ' || nl.recipient_address, ''),
       nl.created_at, 'notification_logs',
       jsonb_build_object('channel', nl.channel, 'template_key', nl.template_key, 'status', nl.status,
                          'provider', nl.provider, 'failure_reason', nl.failure_reason)
FROM __SCHEMA__.notification_logs nl
UNION ALL
SELECT 'addon:' || at.id::text, at.tenant_id, at.admin_user_id, 'addon_transaction', at.id::text, at.type,
       CASE at.type
           WHEN 'purchase' THEN (('Compra de ' || at.amount) || ' créditos de ') || at.addon_type
           WHEN 'consumption' THEN (('Consumo de ' || abs(at.amount)) || ' crédito(s) ') || at.addon_type
           WHEN 'adjustment' THEN 'Ajuste de créditos ' || at.addon_type
           WHEN 'refund' THEN (('Reembolso de ' || abs(at.amount)) || ' créditos ') || at.addon_type
           ELSE (at.type || ' ') || at.addon_type
       END,
       at.created_at, 'addon_transactions',
       jsonb_build_object('addon_type', at.addon_type, 'amount', at.amount, 'balance_after', at.balance_after,
                          'price_cents', at.price_cents, 'payment_reference', at.payment_reference)
FROM public.addon_transactions at
WHERE at.tenant_id = '__TENANT_ID__'::uuid
$vw$),
('v_cashflow_projection', 3, $vw$
CREATE VIEW __SCHEMA__.v_cashflow_projection WITH (security_invoker = true) AS
SELECT gs.mes,
       to_char(gs.mes, 'YYYY-MM') AS mes_label,
       COALESCE(sum(fr.final_amount) FILTER (WHERE fr.type = 'receita'::financial_record_type AND (fr.status = ANY (ARRAY['pending', 'overdue']))), 0) AS receitas_projetadas,
       COALESCE(sum(fr.final_amount) FILTER (WHERE fr.type = 'despesa'::financial_record_type AND (fr.status = ANY (ARRAY['pending', 'overdue']))), 0) AS despesas_projetadas,
       COALESCE(sum(fr.final_amount) FILTER (WHERE fr.type = 'receita'::financial_record_type AND fr.status = 'pending'), 0) AS receitas_pendentes,
       COALESCE(sum(fr.final_amount) FILTER (WHERE fr.type = 'receita'::financial_record_type AND fr.status = 'overdue'), 0) AS receitas_vencidas,
       COALESCE(sum(fr.final_amount) FILTER (WHERE fr.type = 'despesa'::financial_record_type AND fr.status = 'pending'), 0) AS despesas_pendentes,
       COALESCE(sum(fr.final_amount) FILTER (WHERE fr.type = 'despesa'::financial_record_type AND fr.status = 'overdue'), 0) AS despesas_vencidas,
       COALESCE(sum(fr.final_amount) FILTER (WHERE fr.type = 'receita'::financial_record_type AND (fr.status = ANY (ARRAY['pending', 'overdue']))), 0)
       - COALESCE(sum(fr.final_amount) FILTER (WHERE fr.type = 'despesa'::financial_record_type AND (fr.status = ANY (ARRAY['pending', 'overdue']))), 0) AS saldo_projetado,
       count(fr.id) FILTER (WHERE fr.status = ANY (ARRAY['pending', 'overdue'])) AS count_registros
FROM generate_series(date_trunc('month', CURRENT_DATE::timestamp with time zone)::date::timestamp with time zone,
                     (date_trunc('month', CURRENT_DATE::timestamp with time zone) + '5 mons'::interval)::date::timestamp with time zone,
                     '1 mon'::interval) gs(mes)
LEFT JOIN __SCHEMA__.financial_records fr
       ON fr.deleted_at IS NULL
      AND (fr.status = ANY (ARRAY['pending', 'overdue']))
      AND date_trunc('month', fr.due_date::timestamp with time zone)::date = gs.mes
GROUP BY gs.mes
ORDER BY gs.mes
$vw$),
('v_commitment_totals', 4, $vw$
CREATE VIEW __SCHEMA__.v_commitment_totals WITH (security_invoker = true) AS
SELECT '__TENANT_ID__'::uuid AS tenant_id,
       c.id AS commitment_id,
       COALESCE(sum(l.minutes), 0)::integer AS total_minutes
FROM __SCHEMA__.determined_commitments c
LEFT JOIN __SCHEMA__.commitment_time_logs l ON l.commitment_id = c.id
GROUP BY c.id
$vw$),
('v_patient_groups_with_counts', 5, $vw$
CREATE VIEW __SCHEMA__.v_patient_groups_with_counts WITH (security_invoker = true) AS
SELECT pg.id, pg.nome, pg.cor, pg.owner_id, pg.is_system, pg.is_active, pg.created_at, pg.updated_at,
       COALESCE(count(pgp.patient_id), 0)::integer AS patients_count
FROM __SCHEMA__.patient_groups pg
LEFT JOIN __SCHEMA__.patient_group_patient pgp ON pgp.patient_group_id = pg.id
GROUP BY pg.id, pg.nome, pg.cor, pg.owner_id, pg.is_system, pg.is_active, pg.created_at, pg.updated_at
$vw$),
('v_tag_patient_counts', 6, $vw$
CREATE VIEW __SCHEMA__.v_tag_patient_counts WITH (security_invoker = true) AS
SELECT t.id, t.owner_id, t.nome, t.cor, t.is_padrao, t.created_at, t.updated_at,
       COALESCE(count(ppt.patient_id), 0)::integer AS pacientes_count,
       COALESCE(count(ppt.patient_id), 0)::integer AS patient_count
FROM __SCHEMA__.patient_tags t
LEFT JOIN __SCHEMA__.patient_patient_tag ppt ON ppt.tag_id = t.id AND ppt.owner_id = t.owner_id
GROUP BY t.id, t.owner_id, t.nome, t.cor, t.is_padrao, t.created_at, t.updated_at
$vw$);

-- Valida as views instanciando no próprio template (tenant nulo)
DO $$
DECLARE r record;
BEGIN
    PERFORM pg_catalog.set_config('search_path', 'public, pg_catalog', true);
    FOR r IN SELECT * FROM _tenant_template._views ORDER BY position LOOP
        EXECUTE replace(replace(r.definition, '__SCHEMA__', '_tenant_template'),
                        '__TENANT_ID__', '00000000-0000-0000-0000-000000000000');
        RAISE NOTICE 'view _tenant_template.% validada', r.view_name;
    END LOOP;
END $$;

COMMIT;
