-- PostgreSQL Configuration Management Schema for Foxhunt HFT System
-- ==================================================================
-- This migration creates a comprehensive configuration management system
-- with PostgreSQL NOTIFY/LISTEN support for hot-reload capabilities.

-- === EXTENSIONS AND FUNCTIONS ===

-- Enable UUID extension if not already enabled
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";

-- Enable BTREE_GIN for composite indexes
CREATE EXTENSION IF NOT EXISTS btree_gin;

-- Create configuration change notification function
CREATE OR REPLACE FUNCTION notify_config_change()
RETURNS TRIGGER AS $$
DECLARE
    payload JSONB;
BEGIN
    -- Build notification payload
    payload := jsonb_build_object(
        'table', TG_TABLE_NAME,
        'operation', TG_OP,
        'timestamp', EXTRACT(EPOCH FROM NOW()),
        'config_key', COALESCE(NEW.config_key, OLD.config_key),
        'category_path', COALESCE(NEW.category_path, OLD.category_path),
        'environment', COALESCE(NEW.environment, OLD.environment)
    );
    
    -- Add old/new values for updates
    IF TG_OP = 'UPDATE' THEN
        payload := payload || jsonb_build_object(
            'old_value', OLD.config_value,
            'new_value', NEW.config_value,
            'changed_by', NEW.updated_by
        );
    ELSIF TG_OP = 'INSERT' THEN
        payload := payload || jsonb_build_object(
            'new_value', NEW.config_value,
            'created_by', NEW.created_by
        );
    ELSIF TG_OP = 'DELETE' THEN
        payload := payload || jsonb_build_object(
            'old_value', OLD.config_value,
            'deleted_by', CURRENT_USER
        );
    END IF;
    
    -- Send notification on foxhunt_config_changes channel
    PERFORM pg_notify('foxhunt_config_changes', payload::text);
    
    RETURN COALESCE(NEW, OLD);
END;
$$ LANGUAGE plpgsql;

-- === CORE CONFIGURATION TABLES ===

-- Configuration Categories (hierarchical organization)
CREATE TABLE config_categories (
    id SERIAL PRIMARY KEY,
    category_name VARCHAR(100) NOT NULL,
    parent_id INTEGER REFERENCES config_categories(id) ON DELETE CASCADE,
    category_path TEXT NOT NULL, -- Computed path like 'trading.risk.limits'
    description TEXT,
    is_system BOOLEAN NOT NULL DEFAULT false, -- System vs user-defined categories
    display_order INTEGER DEFAULT 0,
    metadata JSONB DEFAULT '{}'::jsonb,
    created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    created_by VARCHAR(100) NOT NULL DEFAULT CURRENT_USER,
    updated_by VARCHAR(100) NOT NULL DEFAULT CURRENT_USER
);

-- Configuration Settings (main configuration storage)
CREATE TABLE config_settings (
    id SERIAL PRIMARY KEY,
    config_key VARCHAR(200) NOT NULL,
    category_id INTEGER NOT NULL REFERENCES config_categories(id) ON DELETE CASCADE,
    category_path TEXT NOT NULL, -- Denormalized for performance
    config_value JSONB NOT NULL, -- Flexible value storage
    value_type VARCHAR(50) NOT NULL DEFAULT 'string', -- string, number, boolean, object, array
    environment VARCHAR(50) NOT NULL DEFAULT 'development', -- development, staging, production
    is_sensitive BOOLEAN NOT NULL DEFAULT false, -- Encrypted/protected values
    is_system BOOLEAN NOT NULL DEFAULT false, -- System vs user-defined settings
    is_readonly BOOLEAN NOT NULL DEFAULT false, -- Immutable settings
    validation_schema JSONB, -- JSON Schema for value validation
    default_value JSONB, -- Default value if not set
    description TEXT,
    tags TEXT[] DEFAULT '{}', -- Searchable tags
    depends_on TEXT[], -- Dependencies on other config keys
    affects TEXT[], -- What this setting affects (services, components)
    hot_reload BOOLEAN NOT NULL DEFAULT true, -- Can be changed without restart
    restart_required BOOLEAN NOT NULL DEFAULT false, -- Requires service restart
    is_active BOOLEAN NOT NULL DEFAULT true, -- Soft delete / deactivation
    version INTEGER NOT NULL DEFAULT 1, -- Version for optimistic locking
    created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    created_by VARCHAR(100) NOT NULL DEFAULT CURRENT_USER,
    updated_by VARCHAR(100) NOT NULL DEFAULT CURRENT_USER,

    -- Constraints
    CONSTRAINT uk_config_settings_key_env UNIQUE (config_key, environment),
    CONSTRAINT chk_value_type CHECK (value_type IN ('string', 'number', 'boolean', 'object', 'array', 'null')),
    CONSTRAINT chk_environment CHECK (environment IN ('development', 'staging', 'production', 'test')),
    CONSTRAINT chk_not_both_readonly_hotreload CHECK (NOT (is_readonly AND hot_reload))
);

-- Configuration History (audit trail)
CREATE TABLE config_history (
    id SERIAL PRIMARY KEY,
    config_setting_id INTEGER NOT NULL REFERENCES config_settings(id) ON DELETE CASCADE,
    config_key VARCHAR(200) NOT NULL,
    category_path TEXT NOT NULL,
    environment VARCHAR(50) NOT NULL,
    old_value JSONB,
    new_value JSONB NOT NULL,
    change_type VARCHAR(20) NOT NULL, -- insert, update, delete
    changed_by VARCHAR(100) NOT NULL,
    change_reason TEXT,
    change_request_id VARCHAR(100), -- External change tracking
    rollback_to_id INTEGER REFERENCES config_history(id), -- For rollbacks
    applied_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    
    -- Constraints
    CONSTRAINT chk_change_type CHECK (change_type IN ('insert', 'update', 'delete', 'rollback'))
);

-- Configuration Environments (environment management)
CREATE TABLE config_environments (
    id SERIAL PRIMARY KEY,
    environment_name VARCHAR(50) NOT NULL UNIQUE,
    display_name VARCHAR(100) NOT NULL,
    description TEXT,
    is_active BOOLEAN NOT NULL DEFAULT true,
    is_production BOOLEAN NOT NULL DEFAULT false,
    inherits_from VARCHAR(50) REFERENCES config_environments(environment_name), -- Environment inheritance
    isolation_level VARCHAR(20) NOT NULL DEFAULT 'strict', -- strict, permissive
    auto_sync BOOLEAN NOT NULL DEFAULT false, -- Auto-sync from parent environment
    metadata JSONB DEFAULT '{}'::jsonb,
    created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    
    -- Constraints
    CONSTRAINT chk_isolation_level CHECK (isolation_level IN ('strict', 'permissive')),
    CONSTRAINT chk_no_self_inherit CHECK (environment_name != inherits_from)
);

-- Configuration Environment Overrides (environment-specific values)
CREATE TABLE config_environment_overrides (
    id SERIAL PRIMARY KEY,
    config_setting_id INTEGER NOT NULL REFERENCES config_settings(id) ON DELETE CASCADE,
    source_environment VARCHAR(50) NOT NULL REFERENCES config_environments(environment_name),
    target_environment VARCHAR(50) NOT NULL REFERENCES config_environments(environment_name),
    config_key VARCHAR(200) NOT NULL,
    override_value JSONB NOT NULL,
    override_reason TEXT,
    is_active BOOLEAN NOT NULL DEFAULT true,
    expires_at TIMESTAMPTZ, -- Temporary overrides
    created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    created_by VARCHAR(100) NOT NULL DEFAULT CURRENT_USER,
    updated_by VARCHAR(100) NOT NULL DEFAULT CURRENT_USER,
    
    -- Constraints
    CONSTRAINT uk_config_overrides_setting_target UNIQUE (config_setting_id, target_environment),
    CONSTRAINT chk_different_environments CHECK (source_environment != target_environment)
);

-- Configuration Subscriptions (services listening for changes)
CREATE TABLE config_subscriptions (
    id SERIAL PRIMARY KEY,
    service_name VARCHAR(100) NOT NULL,
    service_instance_id VARCHAR(100), -- For multiple instances
    config_pattern TEXT NOT NULL, -- Glob pattern for config keys
    category_pattern TEXT, -- Glob pattern for categories
    environment VARCHAR(50) NOT NULL,
    subscription_type VARCHAR(20) NOT NULL DEFAULT 'notify', -- notify, poll, webhook
    endpoint_url TEXT, -- For webhook subscriptions
    is_active BOOLEAN NOT NULL DEFAULT true,
    last_notification_at TIMESTAMPTZ,
    notification_count INTEGER DEFAULT 0,
    created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    
    -- Constraints
    CONSTRAINT chk_subscription_type CHECK (subscription_type IN ('notify', 'poll', 'webhook')),
    CONSTRAINT chk_webhook_endpoint CHECK (
        (subscription_type = 'webhook' AND endpoint_url IS NOT NULL) OR
        (subscription_type != 'webhook')
    )
);

-- Configuration Locks (prevent concurrent modifications)
CREATE TABLE config_locks (
    id SERIAL PRIMARY KEY,
    config_key VARCHAR(200) NOT NULL,
    environment VARCHAR(50) NOT NULL,
    locked_by VARCHAR(100) NOT NULL,
    lock_reason TEXT,
    locked_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    expires_at TIMESTAMPTZ NOT NULL DEFAULT (NOW() + INTERVAL '1 hour'),
    
    -- Constraints
    CONSTRAINT uk_config_locks_key_env UNIQUE (config_key, environment)
);

-- === INDEXES FOR PERFORMANCE ===

-- Config Categories Indexes
CREATE INDEX idx_config_categories_parent ON config_categories(parent_id) WHERE parent_id IS NOT NULL;
CREATE INDEX idx_config_categories_path ON config_categories(category_path);
CREATE UNIQUE INDEX idx_config_categories_path_unique ON config_categories(category_path);

-- Config Settings Indexes (optimized for HFT lookups)
CREATE INDEX idx_config_settings_category ON config_settings(category_id);
CREATE INDEX idx_config_settings_category_path ON config_settings(category_path);
CREATE INDEX idx_config_settings_environment ON config_settings(environment);
CREATE INDEX idx_config_settings_hot_reload ON config_settings(hot_reload) WHERE hot_reload = true;
CREATE INDEX idx_config_settings_system ON config_settings(is_system);
CREATE INDEX idx_config_settings_tags ON config_settings USING GIN(tags);
CREATE INDEX idx_config_settings_depends_on ON config_settings USING GIN(depends_on);
CREATE INDEX idx_config_settings_affects ON config_settings USING GIN(affects);
CREATE INDEX idx_config_settings_updated_at ON config_settings(updated_at DESC);

-- GIN index for JSONB config values (for complex queries)
CREATE INDEX idx_config_settings_value_gin ON config_settings USING GIN(config_value);

-- Composite index for fast config lookups (most common query pattern)
CREATE INDEX idx_config_settings_key_env_lookup ON config_settings(config_key, environment, is_active) 
WHERE is_active = true;

-- Config History Indexes
CREATE INDEX idx_config_history_setting ON config_history(config_setting_id);
CREATE INDEX idx_config_history_key_env ON config_history(config_key, environment);
CREATE INDEX idx_config_history_applied_at ON config_history(applied_at DESC);
CREATE INDEX idx_config_history_changed_by ON config_history(changed_by);

-- Config Environment Overrides Indexes
CREATE INDEX idx_config_overrides_target_env ON config_environment_overrides(target_environment);
CREATE INDEX idx_config_overrides_active ON config_environment_overrides(is_active) WHERE is_active = true;
CREATE INDEX idx_config_overrides_expires ON config_environment_overrides(expires_at) WHERE expires_at IS NOT NULL;

-- Config Subscriptions Indexes
CREATE INDEX idx_config_subscriptions_service ON config_subscriptions(service_name);
CREATE INDEX idx_config_subscriptions_pattern ON config_subscriptions(config_pattern);
CREATE INDEX idx_config_subscriptions_active ON config_subscriptions(is_active) WHERE is_active = true;
CREATE INDEX idx_config_subscriptions_environment ON config_subscriptions(environment);

-- Config Locks Indexes
CREATE INDEX idx_config_locks_expires ON config_locks(expires_at);
CREATE INDEX idx_config_locks_locked_by ON config_locks(locked_by);

-- === TRIGGERS FOR NOTIFICATIONS ===

-- Trigger for config_settings changes
CREATE TRIGGER tr_config_settings_notify
    AFTER INSERT OR UPDATE OR DELETE ON config_settings
    FOR EACH ROW EXECUTE FUNCTION notify_config_change();

-- Trigger for config_environment_overrides changes
CREATE TRIGGER tr_config_overrides_notify
    AFTER INSERT OR UPDATE OR DELETE ON config_environment_overrides
    FOR EACH ROW EXECUTE FUNCTION notify_config_change();

-- === UTILITY FUNCTIONS ===

-- Function to get configuration value with environment inheritance
CREATE OR REPLACE FUNCTION get_config_value(
    p_config_key VARCHAR(200),
    p_environment VARCHAR(50) DEFAULT 'development'
)
RETURNS JSONB AS $$
DECLARE
    result JSONB;
    parent_env VARCHAR(50);
BEGIN
    -- First try to get override value
    SELECT override_value INTO result
    FROM config_environment_overrides ceo
    JOIN config_settings cs ON ceo.config_setting_id = cs.id
    WHERE cs.config_key = p_config_key 
    AND ceo.target_environment = p_environment
    AND ceo.is_active = true
    AND (ceo.expires_at IS NULL OR ceo.expires_at > NOW());
    
    -- If no override, get the regular value
    IF result IS NULL THEN
        SELECT config_value INTO result
        FROM config_settings 
        WHERE config_key = p_config_key 
        AND environment = p_environment
        AND is_active = true;
    END IF;
    
    -- If still no value and environment has parent, try parent
    IF result IS NULL THEN
        SELECT inherits_from INTO parent_env
        FROM config_environments
        WHERE environment_name = p_environment;
        
        IF parent_env IS NOT NULL THEN
            RETURN get_config_value(p_config_key, parent_env);
        END IF;
    END IF;
    
    -- If still no value, try default
    IF result IS NULL THEN
        SELECT default_value INTO result
        FROM config_settings
        WHERE config_key = p_config_key
        AND environment = p_environment;
    END IF;
    
    RETURN result;
END;
$$ LANGUAGE plpgsql;

-- Function to set configuration value with history tracking
CREATE OR REPLACE FUNCTION set_config_value(
    p_config_key VARCHAR(200),
    p_new_value JSONB,
    p_environment VARCHAR(50) DEFAULT 'development',
    p_changed_by VARCHAR(100) DEFAULT CURRENT_USER,
    p_change_reason TEXT DEFAULT NULL
)
RETURNS BOOLEAN AS $$
DECLARE
    setting_id INTEGER;
    old_value JSONB;
    setting_version INTEGER;
BEGIN
    -- Get current setting
    SELECT id, config_value, version INTO setting_id, old_value, setting_version
    FROM config_settings
    WHERE config_key = p_config_key 
    AND environment = p_environment
    AND is_active = true;
    
    -- If setting doesn't exist, return false
    IF setting_id IS NULL THEN
        RETURN false;
    END IF;
    
    -- Check if it's read-only
    IF EXISTS (SELECT 1 FROM config_settings WHERE id = setting_id AND is_readonly = true) THEN
        RAISE EXCEPTION 'Configuration setting "%" is read-only', p_config_key;
    END IF;
    
    -- Update the setting
    UPDATE config_settings 
    SET config_value = p_new_value,
        updated_at = NOW(),
        updated_by = p_changed_by,
        version = version + 1
    WHERE id = setting_id;
    
    -- Record in history
    INSERT INTO config_history (
        config_setting_id, config_key, category_path, environment,
        old_value, new_value, change_type, changed_by, change_reason
    )
    SELECT 
        setting_id, p_config_key, category_path, p_environment,
        old_value, p_new_value, 'update', p_changed_by, p_change_reason
    FROM config_settings
    WHERE id = setting_id;
    
    RETURN true;
END;
$$ LANGUAGE plpgsql;

-- Function to build category path from hierarchy
CREATE OR REPLACE FUNCTION build_category_path(category_id INTEGER)
RETURNS TEXT AS $$
DECLARE
    path TEXT := '';
    current_id INTEGER := category_id;
    current_name VARCHAR(100);
    parent_id INTEGER;
BEGIN
    LOOP
        SELECT category_name, parent_id INTO current_name, parent_id
        FROM config_categories
        WHERE id = current_id;
        
        EXIT WHEN current_name IS NULL;
        
        IF path = '' THEN
            path := current_name;
        ELSE
            path := current_name || '.' || path;
        END IF;
        
        EXIT WHEN parent_id IS NULL;
        current_id := parent_id;
    END LOOP;
    
    RETURN path;
END;
$$ LANGUAGE plpgsql;

-- Trigger to automatically update category_path in config_settings
CREATE OR REPLACE FUNCTION update_config_category_path()
RETURNS TRIGGER AS $$
BEGIN
    -- Update category_path in config_settings when category changes
    UPDATE config_settings 
    SET category_path = build_category_path(category_id),
        updated_at = NOW()
    WHERE category_id = NEW.id;
    
    RETURN NEW;
END;
$$ LANGUAGE plpgsql;

CREATE TRIGGER tr_config_categories_update_path
    AFTER UPDATE ON config_categories
    FOR EACH ROW EXECUTE FUNCTION update_config_category_path();

-- Function to clean up expired locks
CREATE OR REPLACE FUNCTION cleanup_expired_config_locks()
RETURNS INTEGER AS $$
DECLARE
    deleted_count INTEGER;
BEGIN
    DELETE FROM config_locks WHERE expires_at < NOW();
    GET DIAGNOSTICS deleted_count = ROW_COUNT;
    RETURN deleted_count;
END;
$$ LANGUAGE plpgsql;

-- === ROW LEVEL SECURITY ===

-- Enable RLS on sensitive configuration
ALTER TABLE config_settings ENABLE ROW LEVEL SECURITY;

-- Policy: Only allow access to non-sensitive configs for regular users
CREATE POLICY config_settings_non_sensitive_policy ON config_settings
    FOR SELECT USING (NOT is_sensitive OR current_user = 'foxhunt_admin');

-- Policy: Only admins can modify system configs
CREATE POLICY config_settings_system_policy ON config_settings
    FOR ALL USING (NOT is_system OR current_user = 'foxhunt_admin');

-- === PERFORMANCE OPTIMIZATIONS ===

-- Update table statistics for query planning
ANALYZE config_categories;
ANALYZE config_settings;
ANALYZE config_history;
ANALYZE config_environments;
ANALYZE config_environment_overrides;
ANALYZE config_subscriptions;
ANALYZE config_locks;

-- Create performance monitoring view
CREATE OR REPLACE VIEW config_performance_stats AS
SELECT
    schemaname,
    relname as tablename,
    n_tup_ins as inserts,
    n_tup_upd as updates,
    n_tup_del as deletes,
    n_live_tup as live_rows,
    n_dead_tup as dead_rows,
    last_vacuum,
    last_autovacuum,
    last_analyze,
    last_autoanalyze
FROM pg_stat_user_tables
WHERE relname LIKE 'config_%'
ORDER BY relname;

-- === COMMENTS FOR DOCUMENTATION ===

COMMENT ON TABLE config_categories IS 'Hierarchical organization of configuration settings with path-based lookup';
COMMENT ON TABLE config_settings IS 'Main configuration storage with JSONB values, environment support, and hot-reload capabilities';
COMMENT ON TABLE config_history IS 'Complete audit trail of all configuration changes with rollback support';
COMMENT ON TABLE config_environments IS 'Environment definitions with inheritance and isolation controls';
COMMENT ON TABLE config_environment_overrides IS 'Environment-specific configuration overrides with expiration support';
COMMENT ON TABLE config_subscriptions IS 'Service subscription management for configuration change notifications';
COMMENT ON TABLE config_locks IS 'Distributed locking mechanism to prevent concurrent configuration modifications';

COMMENT ON FUNCTION notify_config_change() IS 'Trigger function that sends PostgreSQL NOTIFY messages for configuration changes';
COMMENT ON FUNCTION get_config_value(VARCHAR, VARCHAR) IS 'Retrieves configuration value with environment inheritance and override support';
COMMENT ON FUNCTION set_config_value(VARCHAR, JSONB, VARCHAR, VARCHAR, TEXT) IS 'Updates configuration value with automatic history tracking and validation';
COMMENT ON FUNCTION build_category_path(INTEGER) IS 'Builds dot-separated category path from hierarchical structure';
COMMENT ON FUNCTION cleanup_expired_config_locks() IS 'Removes expired configuration locks (should be called periodically)';

-- Configuration schema created successfully!
-- Features:
-- - Hierarchical configuration categories with path-based organization
-- - JSONB storage for flexible configuration values with type validation
-- - Environment-specific configurations with inheritance
-- - Hot-reload support with PostgreSQL NOTIFY/LISTEN
-- - Complete audit trail with rollback capabilities
-- - Row-level security for sensitive configurations
-- - Performance-optimized indexes for HFT workloads
-- - Distributed locking for concurrent access control
-- - Subscription management for service notifications
-- - Utility functions for configuration management
--
-- Usage:
-- 1. Listen to 'foxhunt_config_changes' channel for real-time updates
-- 2. Use get_config_value('key', 'environment') for configuration retrieval
-- 3. Use set_config_value('key', value, 'environment') for updates
-- 4. Monitor config_performance_stats view for performance metrics