-- Migration 052: Account Linking Schema for Energy Union Platform
-- Version: 2.7
-- Date: 2026-01-18
-- Purpose: Enable Telegram ↔ Energy Union account linking for cross-channel memory

-- ============================================================================
-- 1. ACCOUNT LINKS - Core binding between Telegram and Platform accounts
-- ============================================================================

CREATE TABLE IF NOT EXISTS account_links (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    
    -- Energy Union platform account
    account_id UUID NOT NULL,
    
    -- Telegram identifiers
    telegram_user_id BIGINT NOT NULL UNIQUE,
    telegram_username VARCHAR(255),
    telegram_first_name VARCHAR(255),
    telegram_last_name VARCHAR(255),
    
    -- Linking metadata
    linked_at TIMESTAMPTZ DEFAULT NOW(),
    linked_via VARCHAR(50) DEFAULT 'bot_command', -- bot_command, web_dashboard, api
    link_code_used VARCHAR(64),
    
    -- Status
    status VARCHAR(20) DEFAULT 'active', -- active, suspended, revoked
    revoked_at TIMESTAMPTZ,
    revoked_reason TEXT,
    
    -- Audit
    created_at TIMESTAMPTZ DEFAULT NOW(),
    updated_at TIMESTAMPTZ DEFAULT NOW(),
    
    CONSTRAINT unique_account_telegram UNIQUE (account_id, telegram_user_id)
);

CREATE INDEX idx_account_links_account_id ON account_links(account_id);
CREATE INDEX idx_account_links_telegram_user_id ON account_links(telegram_user_id);
CREATE INDEX idx_account_links_status ON account_links(status);

-- ============================================================================
-- 2. LINK CODES - One-time codes for account linking
-- ============================================================================

CREATE TABLE IF NOT EXISTS link_codes (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    
    -- The code itself
    code VARCHAR(64) NOT NULL UNIQUE,
    
    -- Who generated it
    account_id UUID NOT NULL,
    generated_via VARCHAR(50) DEFAULT 'web_dashboard', -- web_dashboard, api, admin
    
    -- Expiration
    expires_at TIMESTAMPTZ NOT NULL,
    
    -- Usage tracking
    used BOOLEAN DEFAULT FALSE,
    used_at TIMESTAMPTZ,
    used_by_telegram_id BIGINT,
    
    -- Audit
    created_at TIMESTAMPTZ DEFAULT NOW(),
    
    CONSTRAINT link_code_valid CHECK (expires_at > created_at)
);

CREATE INDEX idx_link_codes_code ON link_codes(code);
CREATE INDEX idx_link_codes_account_id ON link_codes(account_id);
CREATE INDEX idx_link_codes_expires ON link_codes(expires_at) WHERE NOT used;

-- ============================================================================
-- 3. USER TIMELINE - Cross-channel interaction history
-- ============================================================================

CREATE TABLE IF NOT EXISTS user_timeline (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    
    -- Account reference (linked user)
    account_id UUID NOT NULL,
    
    -- Source channel
    channel VARCHAR(50) NOT NULL, -- telegram_dm, telegram_group, web_chat, api
    channel_id VARCHAR(255), -- specific chat_id or session_id
    
    -- Event type
    event_type VARCHAR(50) NOT NULL, -- message, command, action, milestone
    
    -- Content
    summary TEXT NOT NULL, -- Short summary, not raw content
    content_hash VARCHAR(64), -- For deduplication
    
    -- Metadata
    metadata JSONB DEFAULT '{}',
    
    -- Importance scoring (for retrieval)
    importance_score FLOAT DEFAULT 0.5,
    
    -- Timestamps
    event_at TIMESTAMPTZ NOT NULL,
    created_at TIMESTAMPTZ DEFAULT NOW(),
    
    -- Retention
    expires_at TIMESTAMPTZ, -- NULL = permanent
    
    CONSTRAINT valid_importance CHECK (importance_score >= 0 AND importance_score <= 1)
);

CREATE INDEX idx_user_timeline_account_id ON user_timeline(account_id);
CREATE INDEX idx_user_timeline_event_at ON user_timeline(event_at DESC);
CREATE INDEX idx_user_timeline_channel ON user_timeline(channel);
CREATE INDEX idx_user_timeline_importance ON user_timeline(importance_score DESC);

-- ============================================================================
-- 4. ORG CHAT MESSAGES - Official chat logging
-- ============================================================================

CREATE TABLE IF NOT EXISTS org_chat_messages (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    
    -- Chat identification
    chat_id BIGINT NOT NULL,
    chat_type VARCHAR(50) NOT NULL, -- official_ops, mentor_room, public_community
    chat_title VARCHAR(255),
    
    -- Message
    message_id BIGINT NOT NULL,
    sender_telegram_id BIGINT,
    sender_account_id UUID, -- Linked account if exists
    sender_username VARCHAR(255),
    sender_display_name VARCHAR(255),
    
    -- Content
    text TEXT,
    has_media BOOLEAN DEFAULT FALSE,
    media_type VARCHAR(50), -- photo, video, document, voice
    attachments_ref JSONB DEFAULT '[]',
    
    -- Reply tracking
    reply_to_message_id BIGINT,
    
    -- Processing status
    processed_for_decisions BOOLEAN DEFAULT FALSE,
    processed_at TIMESTAMPTZ,
    
    -- Timestamps
    message_at TIMESTAMPTZ NOT NULL,
    created_at TIMESTAMPTZ DEFAULT NOW(),
    
    CONSTRAINT unique_chat_message UNIQUE (chat_id, message_id)
);

CREATE INDEX idx_org_chat_messages_chat ON org_chat_messages(chat_id);
CREATE INDEX idx_org_chat_messages_sender ON org_chat_messages(sender_telegram_id);
CREATE INDEX idx_org_chat_messages_at ON org_chat_messages(message_at DESC);
CREATE INDEX idx_org_chat_messages_unprocessed ON org_chat_messages(chat_id) 
    WHERE NOT processed_for_decisions;

-- ============================================================================
-- 5. DECISION RECORDS - Extracted decisions from chats
-- ============================================================================

CREATE TABLE IF NOT EXISTS decision_records (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    
    -- Source
    chat_id BIGINT NOT NULL,
    source_message_id BIGINT NOT NULL,
    
    -- Decision content
    decision TEXT NOT NULL,
    action TEXT,
    owner VARCHAR(255), -- @username or role
    due_date DATE,
    canon_change BOOLEAN DEFAULT FALSE,
    
    -- Status tracking
    status VARCHAR(50) DEFAULT 'pending', -- pending, in_progress, completed, cancelled
    status_updated_at TIMESTAMPTZ,
    status_updated_by VARCHAR(255),
    
    -- Extraction metadata
    extracted_at TIMESTAMPTZ DEFAULT NOW(),
    extraction_method VARCHAR(50) DEFAULT 'regex', -- regex, llm, manual
    confidence_score FLOAT DEFAULT 1.0,
    
    -- Verification
    verified BOOLEAN DEFAULT FALSE,
    verified_at TIMESTAMPTZ,
    verified_by VARCHAR(255),
    
    -- Timestamps
    created_at TIMESTAMPTZ DEFAULT NOW(),
    updated_at TIMESTAMPTZ DEFAULT NOW(),
    
    CONSTRAINT fk_source_message FOREIGN KEY (chat_id, source_message_id) 
        REFERENCES org_chat_messages(chat_id, message_id) ON DELETE CASCADE
);

CREATE INDEX idx_decision_records_chat ON decision_records(chat_id);
CREATE INDEX idx_decision_records_status ON decision_records(status);
CREATE INDEX idx_decision_records_due ON decision_records(due_date) WHERE status NOT IN ('completed', 'cancelled');
CREATE INDEX idx_decision_records_canon ON decision_records(canon_change) WHERE canon_change = TRUE;

-- ============================================================================
-- 6. KYC ATTESTATIONS - Status without PII
-- ============================================================================

CREATE TABLE IF NOT EXISTS kyc_attestations (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    
    -- Account reference
    account_id UUID NOT NULL UNIQUE,
    
    -- Attestation fields (NO RAW PII)
    kyc_status VARCHAR(20) NOT NULL DEFAULT 'unverified', -- unverified, pending, passed, failed
    kyc_provider VARCHAR(100),
    jurisdiction VARCHAR(10), -- ISO country code
    risk_tier VARCHAR(20) DEFAULT 'unknown', -- low, medium, high, unknown
    pep_sanctions_flag BOOLEAN DEFAULT FALSE,
    wallet_verified BOOLEAN DEFAULT FALSE,
    
    -- Timestamps
    attested_at TIMESTAMPTZ,
    expires_at TIMESTAMPTZ, -- Some KYC needs periodic renewal
    
    -- Audit
    created_at TIMESTAMPTZ DEFAULT NOW(),
    updated_at TIMESTAMPTZ DEFAULT NOW(),
    
    CONSTRAINT valid_kyc_status CHECK (kyc_status IN ('unverified', 'pending', 'passed', 'failed')),
    CONSTRAINT valid_risk_tier CHECK (risk_tier IN ('low', 'medium', 'high', 'unknown'))
);

CREATE INDEX idx_kyc_attestations_account ON kyc_attestations(account_id);
CREATE INDEX idx_kyc_attestations_status ON kyc_attestations(kyc_status);

-- ============================================================================
-- 7. HELPER FUNCTIONS
-- ============================================================================

-- Function to resolve Telegram user to account
CREATE OR REPLACE FUNCTION resolve_telegram_account(p_telegram_user_id BIGINT)
RETURNS UUID AS $$
DECLARE
    v_account_id UUID;
BEGIN
    SELECT account_id INTO v_account_id
    FROM account_links
    WHERE telegram_user_id = p_telegram_user_id
      AND status = 'active';
    
    RETURN v_account_id;
END;
$$ LANGUAGE plpgsql;

-- Function to check if link code is valid
CREATE OR REPLACE FUNCTION is_link_code_valid(p_code VARCHAR)
RETURNS TABLE(valid BOOLEAN, account_id UUID, error_message TEXT) AS $$
BEGIN
    RETURN QUERY
    SELECT 
        CASE 
            WHEN lc.id IS NULL THEN FALSE
            WHEN lc.used THEN FALSE
            WHEN lc.expires_at < NOW() THEN FALSE
            ELSE TRUE
        END as valid,
        lc.account_id,
        CASE 
            WHEN lc.id IS NULL THEN 'Code not found'
            WHEN lc.used THEN 'Code already used'
            WHEN lc.expires_at < NOW() THEN 'Code expired'
            ELSE NULL
        END as error_message
    FROM link_codes lc
    WHERE lc.code = p_code;
    
    -- If no rows returned, code doesn't exist
    IF NOT FOUND THEN
        RETURN QUERY SELECT FALSE, NULL::UUID, 'Code not found'::TEXT;
    END IF;
END;
$$ LANGUAGE plpgsql;

-- Function to complete linking
CREATE OR REPLACE FUNCTION complete_account_link(
    p_code VARCHAR,
    p_telegram_user_id BIGINT,
    p_telegram_username VARCHAR DEFAULT NULL,
    p_telegram_first_name VARCHAR DEFAULT NULL,
    p_telegram_last_name VARCHAR DEFAULT NULL
)
RETURNS TABLE(success BOOLEAN, account_id UUID, error_message TEXT) AS $$
DECLARE
    v_account_id UUID;
    v_link_id UUID;
BEGIN
    -- Check code validity
    SELECT lc.account_id INTO v_account_id
    FROM link_codes lc
    WHERE lc.code = p_code
      AND NOT lc.used
      AND lc.expires_at > NOW();
    
    IF v_account_id IS NULL THEN
        RETURN QUERY SELECT FALSE, NULL::UUID, 'Invalid or expired code'::TEXT;
        RETURN;
    END IF;
    
    -- Check if already linked
    IF EXISTS (SELECT 1 FROM account_links WHERE telegram_user_id = p_telegram_user_id AND status = 'active') THEN
        RETURN QUERY SELECT FALSE, NULL::UUID, 'Telegram account already linked'::TEXT;
        RETURN;
    END IF;
    
    -- Create link
    INSERT INTO account_links (
        account_id, telegram_user_id, telegram_username, 
        telegram_first_name, telegram_last_name, link_code_used
    ) VALUES (
        v_account_id, p_telegram_user_id, p_telegram_username,
        p_telegram_first_name, p_telegram_last_name, p_code
    )
    RETURNING id INTO v_link_id;
    
    -- Mark code as used
    UPDATE link_codes SET 
        used = TRUE, 
        used_at = NOW(), 
        used_by_telegram_id = p_telegram_user_id
    WHERE code = p_code;
    
    RETURN QUERY SELECT TRUE, v_account_id, NULL::TEXT;
END;
$$ LANGUAGE plpgsql;

-- Function to add timeline event
CREATE OR REPLACE FUNCTION add_timeline_event(
    p_account_id UUID,
    p_channel VARCHAR,
    p_channel_id VARCHAR,
    p_event_type VARCHAR,
    p_summary TEXT,
    p_metadata JSONB DEFAULT '{}',
    p_importance FLOAT DEFAULT 0.5
)
RETURNS UUID AS $$
DECLARE
    v_event_id UUID;
BEGIN
    INSERT INTO user_timeline (
        account_id, channel, channel_id, event_type, 
        summary, metadata, importance_score, event_at
    ) VALUES (
        p_account_id, p_channel, p_channel_id, p_event_type,
        p_summary, p_metadata, p_importance, NOW()
    )
    RETURNING id INTO v_event_id;
    
    RETURN v_event_id;
END;
$$ LANGUAGE plpgsql;

-- ============================================================================
-- 8. TRIGGERS
-- ============================================================================

-- Update timestamp trigger
CREATE OR REPLACE FUNCTION update_updated_at()
RETURNS TRIGGER AS $$
BEGIN
    NEW.updated_at = NOW();
    RETURN NEW;
END;
$$ LANGUAGE plpgsql;

CREATE TRIGGER update_account_links_timestamp
    BEFORE UPDATE ON account_links
    FOR EACH ROW EXECUTE FUNCTION update_updated_at();

CREATE TRIGGER update_decision_records_timestamp
    BEFORE UPDATE ON decision_records
    FOR EACH ROW EXECUTE FUNCTION update_updated_at();

CREATE TRIGGER update_kyc_attestations_timestamp
    BEFORE UPDATE ON kyc_attestations
    FOR EACH ROW EXECUTE FUNCTION update_updated_at();

-- ============================================================================
-- MIGRATION COMPLETE
-- ============================================================================

-- Insert migration record
INSERT INTO helion_session_state (session_id, state_type, state_data)
VALUES (
    'migration_052',
    'migration',
    jsonb_build_object(
        'version', '052',
        'name', 'account_linking_schema',
        'applied_at', NOW(),
        'tables_created', ARRAY[
            'account_links',
            'link_codes', 
            'user_timeline',
            'org_chat_messages',
            'decision_records',
            'kyc_attestations'
        ]
    )
) ON CONFLICT (session_id) DO UPDATE SET
    state_data = EXCLUDED.state_data,
    updated_at = NOW();
