-- 011_compliance_rules_dynamic.sql
-- Dynamic Compliance Rule Configuration with Hot-Reload Support
-- Implements database-backed rule configuration for ComplianceValidator

-- Enable required extensions
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";

-- Compliance rule types enumeration
CREATE TYPE compliance_rule_type AS ENUM (
    'POSITION_LIMIT',
    'MARKET_ABUSE',
    'CLIENT_SUITABILITY',
    'BEST_EXECUTION',
    'CONCENTRATION_RISK',
    'LEVERAGE_LIMIT',
    'CAPITAL_ADEQUACY',
    'REGULATORY_REPORTING',
    'CUSTOM'
);

-- Main compliance rules configuration table
CREATE TABLE compliance_rules (
    id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
    rule_id VARCHAR(100) UNIQUE NOT NULL,
    name VARCHAR(255) NOT NULL,
    description TEXT NOT NULL,
    rule_type compliance_rule_type NOT NULL,

    -- Rule activation and versioning
    active BOOLEAN NOT NULL DEFAULT true,
    version INTEGER NOT NULL DEFAULT 1,

    -- Severity and priority
    severity VARCHAR(20) NOT NULL CHECK (severity IN ('Info', 'Low', 'Medium', 'High', 'Critical')),
    priority INTEGER NOT NULL DEFAULT 50, -- 0-100 scale

    -- Flexible rule parameters as JSONB
    -- Examples:
    -- Position limit: {"instrument_id": "BTC-USD", "max_position": 1000000, "max_daily_turnover": 5000000}
    -- Market abuse: {"threshold": 1000000, "window_seconds": 300, "check_volume_spike": true}
    -- Client suitability: {"min_net_worth": 100000, "accredited_only": true}
    parameters JSONB NOT NULL DEFAULT '{}',

    -- Regulatory framework references
    regulatory_framework VARCHAR(50), -- MiFID II, Basel III, Dodd-Frank, etc.
    regulatory_reference TEXT, -- Specific article/section reference

    -- Temporal validity
    effective_date TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
    expiry_date TIMESTAMP WITH TIME ZONE,

    -- Audit trail
    created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
    updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
    created_by UUID,
    updated_by UUID,

    -- Additional metadata
    metadata JSONB DEFAULT '{}',

    CONSTRAINT valid_priority CHECK (priority >= 0 AND priority <= 100)
);

-- Compliance rule version history for audit trail
CREATE TABLE compliance_rule_versions (
    id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
    rule_id VARCHAR(100) NOT NULL,
    version INTEGER NOT NULL,

    -- Snapshot of rule at this version
    name VARCHAR(255) NOT NULL,
    description TEXT NOT NULL,
    rule_type compliance_rule_type NOT NULL,
    active BOOLEAN NOT NULL,
    severity VARCHAR(20) NOT NULL,
    priority INTEGER NOT NULL,
    parameters JSONB NOT NULL,
    regulatory_framework VARCHAR(50),
    regulatory_reference TEXT,

    -- Version metadata
    change_reason TEXT,
    changed_by UUID,
    changed_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),

    -- Link to current rule
    current_rule_id UUID REFERENCES compliance_rules(id) ON DELETE CASCADE,

    UNIQUE(rule_id, version)
);

-- Compliance rule execution audit (tracks rule evaluations)
CREATE TABLE compliance_rule_executions (
    id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
    rule_id VARCHAR(100) NOT NULL,

    -- Execution context
    order_id VARCHAR(100),
    instrument_id VARCHAR(50),
    portfolio_id VARCHAR(100),
    client_id VARCHAR(100),

    -- Execution result
    result VARCHAR(20) NOT NULL CHECK (result IN ('PASS', 'WARN', 'FAIL', 'ERROR')),
    violation_detected BOOLEAN NOT NULL DEFAULT false,
    warning_generated BOOLEAN NOT NULL DEFAULT false,

    -- Evaluation details
    evaluated_value JSONB, -- The values that were evaluated
    threshold_value JSONB, -- The threshold that was checked against
    breach_amount DECIMAL(20, 8),

    -- Timing
    execution_timestamp TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
    execution_duration_ms INTEGER,

    -- Additional context
    details JSONB DEFAULT '{}'
);

-- Indexes for performance
CREATE INDEX idx_compliance_rules_active ON compliance_rules(active, rule_type);
CREATE INDEX idx_compliance_rules_type ON compliance_rules(rule_type);
CREATE INDEX idx_compliance_rules_effective ON compliance_rules(effective_date, expiry_date)
    WHERE active = true;
CREATE INDEX idx_compliance_rules_framework ON compliance_rules(regulatory_framework);

CREATE INDEX idx_compliance_rule_versions_rule ON compliance_rule_versions(rule_id, version DESC);
CREATE INDEX idx_compliance_rule_versions_changed ON compliance_rule_versions(changed_at DESC);

CREATE INDEX idx_compliance_rule_executions_rule ON compliance_rule_executions(rule_id, execution_timestamp DESC);
CREATE INDEX idx_compliance_rule_executions_result ON compliance_rule_executions(result, execution_timestamp DESC);
CREATE INDEX idx_compliance_rule_executions_instrument ON compliance_rule_executions(instrument_id, execution_timestamp DESC);

-- Function to archive rule version on update
CREATE OR REPLACE FUNCTION archive_compliance_rule_version()
RETURNS TRIGGER AS $$
BEGIN
    -- Only archive if this is an actual update (not insert)
    IF TG_OP = 'UPDATE' THEN
        INSERT INTO compliance_rule_versions (
            rule_id, version, name, description, rule_type, active,
            severity, priority, parameters, regulatory_framework,
            regulatory_reference, change_reason, changed_by, current_rule_id
        ) VALUES (
            OLD.rule_id, OLD.version, OLD.name, OLD.description, OLD.rule_type,
            OLD.active, OLD.severity, OLD.priority, OLD.parameters,
            OLD.regulatory_framework, OLD.regulatory_reference,
            COALESCE(NEW.metadata->>'change_reason', 'Rule updated'),
            NEW.updated_by, NEW.id
        );

        -- Increment version on the new row
        NEW.version := OLD.version + 1;
        NEW.updated_at := NOW();
    END IF;

    RETURN NEW;
END;
$$ LANGUAGE plpgsql;

CREATE TRIGGER compliance_rule_version_trigger
    BEFORE UPDATE ON compliance_rules
    FOR EACH ROW
    EXECUTE FUNCTION archive_compliance_rule_version();

-- Function to notify on rule changes (for hot-reload)
CREATE OR REPLACE FUNCTION notify_compliance_rule_change()
RETURNS TRIGGER AS $$
DECLARE
    notification_payload TEXT;
BEGIN
    -- Build notification payload
    notification_payload := json_build_object(
        'operation', TG_OP,
        'rule_id', COALESCE(NEW.rule_id, OLD.rule_id),
        'rule_type', COALESCE(NEW.rule_type::text, OLD.rule_type::text),
        'active', COALESCE(NEW.active, false),
        'timestamp', extract(epoch from now())
    )::text;

    -- Send notification on compliance_rules_changed channel
    PERFORM pg_notify('compliance_rules_changed', notification_payload);

    RETURN COALESCE(NEW, OLD);
END;
$$ LANGUAGE plpgsql;

CREATE TRIGGER compliance_rule_change_notify_trigger
    AFTER INSERT OR UPDATE OR DELETE ON compliance_rules
    FOR EACH ROW
    EXECUTE FUNCTION notify_compliance_rule_change();

-- Function to load active compliance rules
CREATE OR REPLACE FUNCTION get_active_compliance_rules()
RETURNS TABLE (
    id UUID,
    rule_id VARCHAR(100),
    name VARCHAR(255),
    description TEXT,
    rule_type compliance_rule_type,
    active BOOLEAN,
    version INTEGER,
    severity VARCHAR(20),
    priority INTEGER,
    parameters JSONB,
    regulatory_framework VARCHAR(50),
    regulatory_reference TEXT,
    effective_date TIMESTAMP WITH TIME ZONE,
    expiry_date TIMESTAMP WITH TIME ZONE
) AS $$
BEGIN
    RETURN QUERY
    SELECT
        cr.id, cr.rule_id, cr.name, cr.description, cr.rule_type,
        cr.active, cr.version, cr.severity, cr.priority, cr.parameters,
        cr.regulatory_framework, cr.regulatory_reference,
        cr.effective_date, cr.expiry_date
    FROM compliance_rules cr
    WHERE cr.active = true
      AND cr.effective_date <= NOW()
      AND (cr.expiry_date IS NULL OR cr.expiry_date > NOW())
    ORDER BY cr.priority DESC, cr.created_at ASC;
END;
$$ LANGUAGE plpgsql STABLE;

-- Function to get rules by type
CREATE OR REPLACE FUNCTION get_compliance_rules_by_type(p_rule_type compliance_rule_type)
RETURNS TABLE (
    id UUID,
    rule_id VARCHAR(100),
    name VARCHAR(255),
    parameters JSONB,
    severity VARCHAR(20)
) AS $$
BEGIN
    RETURN QUERY
    SELECT cr.id, cr.rule_id, cr.name, cr.parameters, cr.severity
    FROM compliance_rules cr
    WHERE cr.active = true
      AND cr.rule_type = p_rule_type
      AND cr.effective_date <= NOW()
      AND (cr.expiry_date IS NULL OR cr.expiry_date > NOW())
    ORDER BY cr.priority DESC;
END;
$$ LANGUAGE plpgsql STABLE;

-- Function to record rule execution
CREATE OR REPLACE FUNCTION record_compliance_rule_execution(
    p_rule_id VARCHAR(100),
    p_result VARCHAR(20),
    p_violation_detected BOOLEAN DEFAULT false,
    p_order_id VARCHAR(100) DEFAULT NULL,
    p_instrument_id VARCHAR(50) DEFAULT NULL,
    p_evaluated_value JSONB DEFAULT NULL,
    p_threshold_value JSONB DEFAULT NULL,
    p_breach_amount DECIMAL DEFAULT NULL
) RETURNS UUID AS $$
DECLARE
    v_execution_id UUID;
BEGIN
    v_execution_id := uuid_generate_v4();

    INSERT INTO compliance_rule_executions (
        id, rule_id, result, violation_detected,
        order_id, instrument_id, evaluated_value,
        threshold_value, breach_amount
    ) VALUES (
        v_execution_id, p_rule_id, p_result, p_violation_detected,
        p_order_id, p_instrument_id, p_evaluated_value,
        p_threshold_value, p_breach_amount
    );

    RETURN v_execution_id;
END;
$$ LANGUAGE plpgsql SECURITY DEFINER;

-- Insert default compliance rules
INSERT INTO compliance_rules (rule_id, name, description, rule_type, severity, priority, parameters, regulatory_framework, regulatory_reference) VALUES
('position_limit_default', 'Default Position Limit', 'Default maximum position size for instruments without specific limits', 'POSITION_LIMIT', 'High', 90,
 '{"max_position_size": 1000000, "max_daily_turnover": 5000000, "concentration_limit": 0.1}'::jsonb,
 'Basel III', 'Capital Requirements'),

('market_abuse_large_order', 'Large Order Market Abuse Detection', 'Detect potentially manipulative large orders', 'MARKET_ABUSE', 'Critical', 95,
 '{"threshold": 1000000, "window_seconds": 300, "volume_spike_multiplier": 3.0}'::jsonb,
 'MAR', 'Market Abuse Regulation Article 15'),

('client_suitability_conservative', 'Conservative Client Suitability', 'Suitability checks for conservative risk profile clients', 'CLIENT_SUITABILITY', 'Medium', 70,
 '{"max_order_value": 10000, "max_leverage": 2.0, "complex_products_allowed": false}'::jsonb,
 'MiFID II', 'Article 25 - Suitability Assessment'),

('best_execution_monitoring', 'Best Execution Monitoring', 'Monitor best execution requirements', 'BEST_EXECUTION', 'High', 85,
 '{"min_execution_quality_score": 0.70, "max_price_improvement_deviation": 0.05, "venue_analysis_required": true}'::jsonb,
 'MiFID II', 'Article 27 - Best Execution'),

('capital_adequacy_basel', 'Basel III Capital Adequacy', 'Monitor capital adequacy ratios', 'CAPITAL_ADEQUACY', 'Critical', 100,
 '{"min_capital_adequacy_ratio": 0.08, "min_leverage_ratio": 0.03, "tier1_minimum": 0.06}'::jsonb,
 'Basel III', 'Capital Adequacy Requirements'),

('concentration_risk_limit', 'Portfolio Concentration Limit', 'Maximum concentration in single instrument', 'CONCENTRATION_RISK', 'High', 80,
 '{"max_concentration_pct": 0.15, "max_sector_concentration": 0.30}'::jsonb,
 'Internal Risk', 'Risk Management Policy Section 4.2');

-- Grant permissions
GRANT SELECT ON compliance_rules TO authenticated_users;
GRANT SELECT ON compliance_rule_versions TO authenticated_users;
GRANT INSERT ON compliance_rule_executions TO authenticated_users;
GRANT EXECUTE ON FUNCTION get_active_compliance_rules TO authenticated_users;
GRANT EXECUTE ON FUNCTION get_compliance_rules_by_type TO authenticated_users;
GRANT EXECUTE ON FUNCTION record_compliance_rule_execution TO authenticated_users;

-- Comments for documentation
COMMENT ON TABLE compliance_rules IS 'Dynamic compliance rules with hot-reload support via PostgreSQL NOTIFY/LISTEN';
COMMENT ON TABLE compliance_rule_versions IS 'Version history for compliance rule changes - full audit trail';
COMMENT ON TABLE compliance_rule_executions IS 'Audit trail of compliance rule evaluations';
COMMENT ON FUNCTION notify_compliance_rule_change IS 'Triggers PostgreSQL NOTIFY on compliance rule changes for hot-reload';
COMMENT ON FUNCTION get_active_compliance_rules IS 'Returns all currently active compliance rules';
COMMENT ON FUNCTION get_compliance_rules_by_type IS 'Returns active rules filtered by type';
