-- PBX Multi-Tenant Realtime Views for Asterisk ODBC
-- Executed against the 'asterisk' database on PostgreSQL VIP 172.17.3.14
-- Prerequisites: postgres_fdw extension, asterisk_reader user

-- ============================================================================
-- Step 1: Create asterisk_reader user (run as superuser on primary)
-- ============================================================================
DO $$
BEGIN
    IF NOT EXISTS (SELECT FROM pg_roles WHERE rolname = 'asterisk_reader') THEN
        CREATE ROLE asterisk_reader WITH LOGIN PASSWORD 'PLACEHOLDER_CHANGE_ME';
    END IF;
END $$;

-- ============================================================================
-- Step 2: Install extensions
-- ============================================================================
CREATE EXTENSION IF NOT EXISTS postgres_fdw;
CREATE EXTENSION IF NOT EXISTS pgcrypto;

-- ============================================================================
-- Step 3: Create foreign server pointing to gsc_admin database
-- ============================================================================
DROP SERVER IF EXISTS gsc_admin_server CASCADE;
CREATE SERVER gsc_admin_server
    FOREIGN DATA WRAPPER postgres_fdw
    OPTIONS (host '127.0.0.1', port '5432', dbname 'gsc_admin');

-- User mapping: asterisk DB superuser -> gsc_admin reader
CREATE USER MAPPING IF NOT EXISTS FOR CURRENT_USER
    SERVER gsc_admin_server
    OPTIONS (user 'asterisk_reader', password 'PLACEHOLDER_CHANGE_ME');

-- ============================================================================
-- Step 4: Create foreign tables mirroring gsc_admin pbx_* tables
-- ============================================================================
DROP SCHEMA IF EXISTS gsc CASCADE;
CREATE SCHEMA gsc;

-- pbx_trunks
CREATE FOREIGN TABLE gsc.pbx_trunks (
    id uuid,
    tenant_id uuid,
    provider_id uuid,
    name varchar(128),
    trunk_type varchar(16),
    status varchar(16),
    host varchar(256),
    port int,
    transport varchar(8),
    username varchar(128),
    password_encrypted bytea,
    auth_realm varchar(128),
    from_domain varchar(256),
    from_user varchar(128),
    register boolean,
    register_frequency int,
    codecs text[],
    dtmf_mode varchar(16),
    nat_mode varchar(16),
    max_channels int,
    context varchar(64),
    trust_id_inbound boolean,
    trust_id_outbound boolean,
    send_pai boolean,
    send_rpid boolean,
    send_diversion boolean,
    direct_media boolean,
    rtp_symmetric boolean,
    rewrite_contact boolean,
    ice_support boolean,
    force_rport boolean,
    timers varchar(16),
    timers_min_se int,
    timers_sess_expires int,
    media_encryption varchar(16),
    media_encryption_optimistic boolean,
    max_contacts int,
    default_expiration int,
    minimum_expiration int,
    maximum_expiration int,
    qualify_timeout numeric(5,2),
    match_by_ip boolean,
    match_ips text[],
    outbound_caller_id_name varchar(64),
    outbound_caller_id_number varchar(32),
    t38_udptl boolean,
    t38_udptl_ec varchar(16),
    t38_udptl_maxdatagram int,
    t38_udptl_nat boolean,
    fax_detect boolean,
    rtp_timeout int,
    rtp_timeout_hold int,
    rtp_keepalive int,
    inband_progress boolean,
    contact_user varchar(128),
    line boolean,
    client_uri varchar(256),
    server_uri varchar(256),
    outbound_proxy varchar(256),
    registration_expiration int,
    registration_retry_interval int,
    pjsip_options jsonb,
    is_active boolean,
    created_at timestamptz,
    updated_at timestamptz
) SERVER gsc_admin_server
OPTIONS (schema_name 'public', table_name 'pbx_trunks');

-- pbx_trunk_dids
CREATE FOREIGN TABLE gsc.pbx_trunk_dids (
    id uuid,
    trunk_id uuid,
    tenant_id uuid,
    did_number varchar(32),
    description varchar(256),
    destination_type varchar(32),
    destination_id uuid,
    is_active boolean
) SERVER gsc_admin_server
OPTIONS (schema_name 'public', table_name 'pbx_trunk_dids');

-- pbx_extensions
CREATE FOREIGN TABLE gsc.pbx_extensions (
    id uuid,
    tenant_id uuid,
    user_id uuid,
    extension varchar(16),
    name varchar(128),
    extension_type varchar(16),
    caller_id_name varchar(64),
    caller_id_number varchar(32),
    outbound_caller_id_number varchar(32),
    sip_username varchar(64),
    sip_password_encrypted bytea,
    transport varchar(8),
    codecs text[],
    dtmf_mode varchar(16),
    nat_mode varchar(16),
    ring_timeout int,
    call_waiting boolean,
    dnd_enabled boolean,
    forward_all_enabled boolean,
    forward_all_destination varchar(64),
    forward_busy_enabled boolean,
    forward_busy_destination varchar(64),
    forward_no_answer_enabled boolean,
    forward_no_answer_destination varchar(64),
    forward_no_answer_timeout int,
    voicemail_enabled boolean,
    voicemail_id uuid,
    recording_mode varchar(16),
    pjsip_options jsonb,
    is_active boolean,
    created_at timestamptz,
    updated_at timestamptz
) SERVER gsc_admin_server
OPTIONS (schema_name 'public', table_name 'pbx_extensions');

-- pbx_voicemail_boxes
CREATE FOREIGN TABLE gsc.pbx_voicemail_boxes (
    id uuid,
    tenant_id uuid,
    extension_id uuid,
    mailbox varchar(16),
    name varchar(128),
    pin_encrypted bytea,
    email varchar(256),
    email_attachment boolean,
    email_delete_after_send boolean,
    pager_email varchar(256),
    max_message_length int,
    max_messages int,
    say_caller_id boolean,
    say_duration boolean,
    review_enabled boolean,
    operator_enabled boolean,
    envelope_enabled boolean,
    timezone varchar(64),
    mwi_enabled boolean,
    is_active boolean,
    created_at timestamptz
) SERVER gsc_admin_server
OPTIONS (schema_name 'public', table_name 'pbx_voicemail_boxes');

-- pbx_inbound_routes
CREATE FOREIGN TABLE gsc.pbx_inbound_routes (
    id uuid,
    tenant_id uuid,
    name varchar(128),
    did_number varchar(32),
    caller_id_pattern varchar(64),
    destination_type varchar(32),
    destination_id uuid,
    destination_data varchar(256),
    priority int,
    is_active boolean,
    trunk_id uuid
) SERVER gsc_admin_server
OPTIONS (schema_name 'public', table_name 'pbx_inbound_routes');

-- pbx_outbound_routes
CREATE FOREIGN TABLE gsc.pbx_outbound_routes (
    id uuid,
    tenant_id uuid,
    name varchar(128),
    priority int,
    dial_patterns text[],
    prepend varchar(16),
    prefix varchar(16),
    strip_digits int,
    override_caller_id boolean,
    caller_id_name varchar(64),
    caller_id_number varchar(32),
    is_emergency boolean,
    is_active boolean
) SERVER gsc_admin_server
OPTIONS (schema_name 'public', table_name 'pbx_outbound_routes');

-- pbx_outbound_route_trunks
CREATE FOREIGN TABLE gsc.pbx_outbound_route_trunks (
    id uuid,
    outbound_route_id uuid,
    trunk_id uuid,
    priority int
) SERVER gsc_admin_server
OPTIONS (schema_name 'public', table_name 'pbx_outbound_route_trunks');

-- pbx_tenant_settings
CREATE FOREIGN TABLE gsc.pbx_tenant_settings (
    id uuid,
    tenant_id uuid,
    extension_length int,
    extension_start int,
    default_codecs text[],
    default_outbound_cid_name varchar(64),
    default_outbound_cid_number varchar(32),
    default_recording_mode varchar(16),
    voicemail_email_from varchar(256),
    default_moh_class varchar(64),
    default_language varchar(8),
    timezone varchar(64),
    max_extensions int,
    max_trunks int,
    max_concurrent_calls int
) SERVER gsc_admin_server
OPTIONS (schema_name 'public', table_name 'pbx_tenant_settings');

-- pbx_queues
CREATE FOREIGN TABLE gsc.pbx_queues (
    id uuid,
    tenant_id uuid,
    extension varchar(16),
    name varchar(128),
    strategy varchar(32),
    timeout int,
    retry int,
    wrapup_time int,
    max_wait_time int,
    max_callers int,
    moh_class varchar(64),
    is_active boolean
) SERVER gsc_admin_server
OPTIONS (schema_name 'public', table_name 'pbx_queues');

-- pbx_queue_members
CREATE FOREIGN TABLE gsc.pbx_queue_members (
    id uuid,
    queue_id uuid,
    extension_id uuid,
    penalty int,
    paused boolean,
    dynamic boolean,
    membername varchar(128),
    interface varchar(128),
    state_interface varchar(128)
) SERVER gsc_admin_server
OPTIONS (schema_name 'public', table_name 'pbx_queue_members');

-- tenants (for name resolution)
CREATE FOREIGN TABLE gsc.tenants (
    id uuid,
    code varchar(32),
    name varchar(128),
    is_active boolean
) SERVER gsc_admin_server
OPTIONS (schema_name 'public', table_name 'tenants');

-- ============================================================================
-- Step 5: Create PJSIP Sorcery Views (Asterisk Realtime)
-- ============================================================================

-- ps_endpoints: PJSIP endpoint objects for trunks
CREATE OR REPLACE VIEW public.ps_endpoints AS
-- Trunk endpoints
SELECT
    t.id::text AS id,
    'endpoint' AS type,
    COALESCE(t.transport, 'udp')::text AS transport,
    COALESCE(t.context, 'from-trunk-' || ten.code) AS context,
    CASE
        WHEN array_length(t.codecs, 1) > 0
        THEN array_to_string(t.codecs, ',')
        ELSE 'ulaw,alaw'
    END AS allow,
    'all' AS disallow,
    t.username AS auth,
    t.id::text AS aors,
    COALESCE(t.outbound_caller_id_name, '') || ' <' || COALESCE(t.outbound_caller_id_number, '') || '>' AS callerid,
    CASE WHEN t.direct_media THEN 'yes' ELSE 'no' END AS direct_media,
    CASE WHEN COALESCE(t.rtp_symmetric, true) THEN 'yes' ELSE 'no' END AS rtp_symmetric,
    CASE WHEN COALESCE(t.force_rport, true) THEN 'yes' ELSE 'no' END AS force_rport,
    CASE WHEN COALESCE(t.rewrite_contact, true) THEN 'yes' ELSE 'no' END AS rewrite_contact,
    CASE WHEN t.trust_id_inbound THEN 'yes' ELSE 'no' END AS trust_id_inbound,
    CASE WHEN t.send_rpid THEN 'yes' ELSE 'no' END AS send_rpid,
    CASE WHEN t.send_pai THEN 'yes' ELSE 'no' END AS send_pai,
    COALESCE(t.dtmf_mode, 'rfc4733')::text AS dtmf_mode,
    CASE WHEN t.ice_support THEN 'yes' ELSE 'no' END AS ice_support,
    CASE WHEN t.t38_udptl THEN 'yes' ELSE 'no' END AS t38_udptl,
    COALESCE(t.t38_udptl_ec, 'none')::text AS t38_udptl_ec,
    COALESCE(t.t38_udptl_maxdatagram, 0) AS t38_udptl_maxdatagram,
    CASE WHEN t.t38_udptl_nat THEN 'yes' ELSE 'no' END AS t38_udptl_nat,
    CASE WHEN COALESCE(t.inband_progress, false) THEN 'yes' ELSE 'no' END AS inband_progress,
    COALESCE(t.timers, 'yes')::text AS timers,
    COALESCE(t.timers_min_se, 90) AS timers_min_se,
    COALESCE(t.timers_sess_expires, 1800) AS timers_sess_expires,
    COALESCE(t.rtp_timeout, 60) AS rtp_timeout,
    COALESCE(t.rtp_timeout_hold, 300) AS rtp_timeout_hold,
    COALESCE(t.rtp_keepalive, 0) AS rtp_keepalive,
    COALESCE(t.media_encryption, 'no')::text AS media_encryption,
    CASE WHEN t.media_encryption_optimistic THEN 'yes' ELSE 'no' END AS media_encryption_optimistic,
    t.tenant_id
FROM gsc.pbx_trunks t
JOIN gsc.tenants ten ON ten.id = t.tenant_id
WHERE t.is_active = true AND t.status = 'active';

-- ps_auths: PJSIP auth objects for trunks
CREATE OR REPLACE VIEW public.ps_auths AS
SELECT
    t.id::text AS id,
    'auth' AS type,
    'userpass' AS auth_type,
    t.username,
    -- Password: [REDACTED] as AES-256-GCM encrypted bytea in gsc_admin.
    -- For ODBC realtime we store a plaintext copy in a separate field.
    -- This view returns the username; actual password set via ops-api sync.
    ''::text AS password,
    COALESCE(t.auth_realm, t.from_domain, t.host, '')::text AS realm,
    t.tenant_id
FROM gsc.pbx_trunks t
JOIN gsc.tenants ten ON ten.id = t.tenant_id
WHERE t.is_active = true AND t.status = 'active'
  AND t.username IS NOT NULL AND t.username != '';

-- ps_aors: PJSIP AOR (Address of Record) objects for trunks
CREATE OR REPLACE VIEW public.ps_aors AS
SELECT
    t.id::text AS id,
    'aor' AS type,
    'sip:' || t.host || ':' || COALESCE(t.port, 5060) AS contact,
    COALESCE(t.max_contacts, 1) AS max_contacts,
    COALESCE(t.qualify_timeout, 3.0)::numeric(5,2) AS qualify_timeout,
    30 AS qualify_frequency,
    COALESCE(t.default_expiration, 3600) AS default_expiration,
    COALESCE(t.minimum_expiration, 60) AS minimum_expiration,
    COALESCE(t.maximum_expiration, 7200) AS maximum_expiration,
    t.tenant_id
FROM gsc.pbx_trunks t
WHERE t.is_active = true AND t.status = 'active';

-- ps_endpoint_id_ips: IP-based endpoint identification for trunks
CREATE OR REPLACE VIEW public.ps_endpoint_id_ips AS
SELECT
    t.id::text AS endpoint,
    ip AS match,
    t.tenant_id
FROM gsc.pbx_trunks t,
     LATERAL unnest(t.match_ips) AS ip
WHERE t.is_active = true AND t.status = 'active'
  AND t.match_by_ip = true
  AND t.match_ips IS NOT NULL
  AND array_length(t.match_ips, 1) > 0;

-- ps_registrations: outbound registration objects for trunks that register
CREATE OR REPLACE VIEW public.ps_registrations AS
SELECT
    t.id::text AS id,
    'registration' AS type,
    COALESCE(t.transport, 'udp')::text AS transport,
    COALESCE(t.outbound_proxy, '')::text AS outbound_proxy,
    COALESCE(t.server_uri, 'sip:' || t.host || ':' || COALESCE(t.port, 5060))::text AS server_uri,
    COALESCE(t.client_uri, 'sip:' || t.username || '@' || COALESCE(t.from_domain, t.host))::text AS client_uri,
    COALESCE(t.contact_user, t.username, '')::text AS contact_user,
    COALESCE(t.registration_expiration, 3600) AS expiration,
    COALESCE(t.registration_retry_interval, 60) AS retry_interval,
    CASE WHEN COALESCE(t.line, false) THEN 'yes' ELSE 'no' END AS line,
    t.id::text AS outbound_auth,
    t.tenant_id
FROM gsc.pbx_trunks t
WHERE t.is_active = true AND t.status = 'active'
  AND t.register = true;

-- voicemail: Asterisk voicemail boxes
CREATE OR REPLACE VIEW public.voicemail AS
SELECT
    vm.id::text AS uniqueid,
    ten.code AS context,
    vm.mailbox,
    ''::text AS password,
    vm.name AS fullname,
    COALESCE(vm.email, '') AS email,
    COALESCE(vm.pager_email, '') AS pager,
    CASE WHEN COALESCE(vm.email_attachment, true) THEN 'yes' ELSE 'no' END AS attach,
    CASE WHEN COALESCE(vm.email_delete_after_send, false) THEN 'yes' ELSE 'no' END AS "delete",
    COALESCE(vm.max_message_length, 180) AS maxsecs,
    COALESCE(vm.max_messages, 100) AS maxmsg,
    COALESCE(vm.timezone, 'UTC') AS tz,
    CASE WHEN COALESCE(vm.say_caller_id, true) THEN 'yes' ELSE 'no' END AS saycid,
    CASE WHEN COALESCE(vm.say_duration, true) THEN 'yes' ELSE 'no' END AS sayduration,
    CASE WHEN COALESCE(vm.review_enabled, true) THEN 'yes' ELSE 'no' END AS review,
    CASE WHEN COALESCE(vm.operator_enabled, true) THEN 'yes' ELSE 'no' END AS operator,
    CASE WHEN COALESCE(vm.envelope_enabled, true) THEN 'yes' ELSE 'no' END AS envelope,
    vm.tenant_id
FROM gsc.pbx_voicemail_boxes vm
JOIN gsc.tenants ten ON ten.id = vm.tenant_id
WHERE vm.is_active = true;

-- ============================================================================
-- Step 6: func_odbc lookup views
-- ============================================================================

-- Tenant lookup by DID number
CREATE OR REPLACE VIEW public.did_tenant_lookup AS
SELECT
    td.did_number,
    td.tenant_id,
    ten.code AS tenant_code
FROM gsc.pbx_trunk_dids td
JOIN gsc.tenants ten ON ten.id = td.tenant_id
WHERE td.is_active = true;

-- Inbound route lookup by DID
CREATE OR REPLACE VIEW public.inbound_route_lookup AS
SELECT
    ir.did_number,
    ir.tenant_id,
    ten.code AS tenant_code,
    ir.destination_type,
    ir.destination_id,
    ir.destination_data,
    ir.priority
FROM gsc.pbx_inbound_routes ir
JOIN gsc.tenants ten ON ten.id = ir.tenant_id
WHERE ir.is_active = true
ORDER BY ir.priority ASC;

-- Outbound route lookup with trunk priority
CREATE OR REPLACE VIEW public.outbound_route_lookup AS
SELECT
    obr.id AS route_id,
    obr.tenant_id,
    ten.code AS tenant_code,
    obr.dial_patterns,
    obr.prepend,
    obr.prefix,
    obr.strip_digits,
    obr.priority AS route_priority,
    obr.override_caller_id,
    obr.caller_id_name,
    obr.caller_id_number,
    obr.is_emergency,
    ort.trunk_id,
    ort.priority AS trunk_priority,
    t.host AS trunk_host,
    t.port AS trunk_port,
    t.username AS trunk_username
FROM gsc.pbx_outbound_routes obr
JOIN gsc.tenants ten ON ten.id = obr.tenant_id
LEFT JOIN gsc.pbx_outbound_route_trunks ort ON ort.outbound_route_id = obr.id
LEFT JOIN gsc.pbx_trunks t ON t.id = ort.trunk_id
WHERE obr.is_active = true
ORDER BY obr.priority ASC, ort.priority ASC;

-- ============================================================================
-- Step 7: SIP password sync table (plaintext for Asterisk ODBC)
-- ============================================================================
-- Asterisk cannot decrypt AES-256-GCM from the DB. We keep a sync table
-- that ops-api populates with plaintext passwords.
CREATE TABLE IF NOT EXISTS public.sip_passwords (
    entity_id uuid PRIMARY KEY,
    entity_type varchar(16) NOT NULL DEFAULT 'trunk', -- 'trunk' or 'extension'
    password text NOT NULL,
    updated_at timestamptz NOT NULL DEFAULT NOW()
);

-- Update ps_auths to use sip_passwords
CREATE OR REPLACE VIEW public.ps_auths AS
SELECT
    t.id::text AS id,
    'auth' AS type,
    'userpass' AS auth_type,
    t.username,
    COALESCE(sp.password, '')::text AS password,
    COALESCE(t.auth_realm, t.from_domain, t.host, '')::text AS realm,
    t.tenant_id
FROM gsc.pbx_trunks t
JOIN gsc.tenants ten ON ten.id = t.tenant_id
LEFT JOIN public.sip_passwords sp ON sp.entity_id = t.id AND sp.entity_type = 'trunk'
WHERE t.is_active = true AND t.status = 'active'
  AND t.username IS NOT NULL AND t.username != '';

-- ============================================================================
-- Step 8: Grant permissions
-- ============================================================================
GRANT USAGE ON SCHEMA public TO asterisk_reader;
GRANT USAGE ON SCHEMA gsc TO asterisk_reader;
GRANT SELECT ON ALL TABLES IN SCHEMA public TO asterisk_reader;
GRANT SELECT ON ALL TABLES IN SCHEMA gsc TO asterisk_reader;
ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT SELECT ON TABLES TO asterisk_reader;

-- Allow asterisk_reader to read sip_passwords (and in future, insert/update via ops-api)
GRANT SELECT, INSERT, UPDATE, DELETE ON public.sip_passwords TO asterisk_reader;

-- ============================================================================
-- Step 9: pg_hba.conf additions (must be done via Patroni config)
-- Add these lines to patroni.yml postgresql.pg_hba section:
--   - host asterisk asterisk_reader 172.17.6.40/32 scram-sha-256
--   - host asterisk asterisk_reader 172.17.6.41/32 scram-sha-256
--   - host asterisk asterisk_reader 172.17.8.20/32 scram-sha-256
--   - host gsc_admin asterisk_reader 172.17.3.11/32 scram-sha-256
--   - host gsc_admin asterisk_reader 172.17.3.12/32 scram-sha-256
--   - host gsc_admin asterisk_reader 172.17.3.13/32 scram-sha-256
-- ============================================================================
