-- ============================================================================
-- PRODUCTION DATABASE ROLES AND SECURITY SETUP
-- ============================================================================
-- Migration: 999_production_roles_setup.sql
-- Purpose: Create production database roles and enable Row Level Security (RLS)
-- Created: Wave 79 Agent 8
-- Status: Production Critical
-- ============================================================================

-- ============================================================================
-- STEP 1: CREATE PRODUCTION ROLES
-- ============================================================================

-- Create authenticated_users role (used by RLS policies)
DO $$
BEGIN
    IF NOT EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'authenticated_users') THEN
        CREATE ROLE authenticated_users;
        RAISE NOTICE 'Created role: authenticated_users';
    ELSE
        RAISE NOTICE 'Role authenticated_users already exists';
    END IF;
END
$$;

-- Create foxhunt_user role (application user with login)
DO $$
BEGIN
    IF NOT EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'foxhunt_user') THEN
        -- NOTE: Password should be changed in production via:
        -- ALTER ROLE foxhunt_user WITH PASSWORD 'strong_production_password';
        CREATE ROLE foxhunt_user LOGIN PASSWORD 'change_me_in_production';
        RAISE NOTICE 'Created role: foxhunt_user';
    ELSE
        RAISE NOTICE 'Role foxhunt_user already exists';
    END IF;
END
$$;

-- Grant authenticated_users role to foxhunt_user
GRANT authenticated_users TO foxhunt_user;

-- Create additional operational roles
DO $$
BEGIN
    IF NOT EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'admin') THEN
        CREATE ROLE admin;
        RAISE NOTICE 'Created role: admin';
    ELSE
        RAISE NOTICE 'Role admin already exists';
    END IF;

    IF NOT EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'compliance_officer') THEN
        CREATE ROLE compliance_officer;
        RAISE NOTICE 'Created role: compliance_officer';
    ELSE
        RAISE NOTICE 'Role compliance_officer already exists';
    END IF;

    IF NOT EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'risk_manager') THEN
        CREATE ROLE risk_manager;
        RAISE NOTICE 'Created role: risk_manager';
    ELSE
        RAISE NOTICE 'Role risk_manager already exists';
    END IF;

    IF NOT EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'trader') THEN
        CREATE ROLE trader;
        RAISE NOTICE 'Created role: trader';
    ELSE
        RAISE NOTICE 'Role trader already exists';
    END IF;

    IF NOT EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'system') THEN
        CREATE ROLE system;
        RAISE NOTICE 'Created role: system';
    ELSE
        RAISE NOTICE 'Role system already exists';
    END IF;
END
$$;

-- ============================================================================
-- STEP 2: VERIFY REQUIRED FUNCTIONS EXIST
-- ============================================================================

-- Check if has_role function exists (used by RLS policies)
DO $$
BEGIN
    IF NOT EXISTS (
        SELECT 1 FROM pg_proc
        WHERE proname = 'has_role'
    ) THEN
        -- Create has_role function if it doesn't exist
        CREATE OR REPLACE FUNCTION has_role(role_name TEXT)
        RETURNS BOOLEAN AS $func$
        BEGIN
            -- Check if current user has the specified role
            RETURN pg_has_role(CURRENT_USER, role_name, 'MEMBER');
        EXCEPTION
            WHEN undefined_object THEN
                RETURN FALSE;
        END;
        $func$ LANGUAGE plpgsql SECURITY DEFINER;

        RAISE NOTICE 'Created function: has_role';
    ELSE
        RAISE NOTICE 'Function has_role already exists';
    END IF;
END
$$;

-- Check if current_user_id function exists (used by RLS policies)
DO $$
BEGIN
    IF NOT EXISTS (
        SELECT 1 FROM pg_proc
        WHERE proname = 'current_user_id'
    ) THEN
        -- Create current_user_id function if it doesn't exist
        CREATE OR REPLACE FUNCTION current_user_id()
        RETURNS UUID AS $func$
        BEGIN
            -- Get user_id from session variable
            RETURN current_setting('app.current_user_id', true)::uuid;
        EXCEPTION
            WHEN OTHERS THEN
                RETURN NULL;
        END;
        $func$ LANGUAGE plpgsql SECURITY DEFINER;

        RAISE NOTICE 'Created function: current_user_id';
    ELSE
        RAISE NOTICE 'Function current_user_id already exists';
    END IF;
END
$$;

-- ============================================================================
-- STEP 3: ENABLE ROW LEVEL SECURITY ON AUDIT TABLES
-- ============================================================================

-- Enable RLS on compliance audit tables (from 010_compliance_audit_trails.sql)
ALTER TABLE sox_trade_audit ENABLE ROW LEVEL SECURITY;
ALTER TABLE mifid_transaction_report ENABLE ROW LEVEL SECURITY;
ALTER TABLE position_limits_audit ENABLE ROW LEVEL SECURITY;
ALTER TABLE kill_switch_audit ENABLE ROW LEVEL SECURITY;
ALTER TABLE best_execution_analysis ENABLE ROW LEVEL SECURITY;

-- Enable RLS on transaction audit events (from 020_transaction_audit_events.sql)
ALTER TABLE transaction_audit_events ENABLE ROW LEVEL SECURITY;

-- Enable RLS on MFA tables if they exist (from 017_mfa_totp_implementation.sql)
DO $$
BEGIN
    IF EXISTS (SELECT 1 FROM pg_tables WHERE tablename = 'mfa_config') THEN
        ALTER TABLE mfa_config ENABLE ROW LEVEL SECURITY;
        ALTER TABLE mfa_backup_codes ENABLE ROW LEVEL SECURITY;
        ALTER TABLE mfa_verification_log ENABLE ROW LEVEL SECURITY;
        ALTER TABLE mfa_enrollment_sessions ENABLE ROW LEVEL SECURITY;
        RAISE NOTICE 'Enabled RLS on MFA tables';
    ELSE
        RAISE NOTICE 'MFA tables not found, skipping RLS for MFA';
    END IF;
END
$$;

-- ============================================================================
-- STEP 4: CREATE RLS POLICIES
-- ============================================================================

-- Drop existing policies if they exist (idempotent)
DROP POLICY IF EXISTS sox_trade_audit_user_policy ON sox_trade_audit;
DROP POLICY IF EXISTS mifid_transaction_user_policy ON mifid_transaction_report;
DROP POLICY IF EXISTS position_limits_user_policy ON position_limits_audit;
DROP POLICY IF EXISTS kill_switch_restricted_policy ON kill_switch_audit;
DROP POLICY IF EXISTS best_execution_policy ON best_execution_analysis;
DROP POLICY IF EXISTS audit_events_user_policy ON transaction_audit_events;
DROP POLICY IF EXISTS audit_events_insert_policy ON transaction_audit_events;

-- RLS Policies for SOX Trade Audit
CREATE POLICY sox_trade_audit_user_policy ON sox_trade_audit
    FOR ALL TO authenticated_users
    USING (user_id = current_user_id() OR has_role('admin') OR has_role('compliance_officer') OR has_role('risk_manager'));

-- RLS Policies for MiFID II Transaction Report
CREATE POLICY mifid_transaction_user_policy ON mifid_transaction_report
    FOR ALL TO authenticated_users
    USING (has_role('admin') OR has_role('compliance_officer') OR has_role('trader'));

-- RLS Policies for Position Limits
CREATE POLICY position_limits_user_policy ON position_limits_audit
    FOR ALL TO authenticated_users
    USING (user_id = current_user_id() OR has_role('admin') OR has_role('risk_manager'));

-- RLS Policies for Kill Switch (Admin and Risk Managers only)
CREATE POLICY kill_switch_restricted_policy ON kill_switch_audit
    FOR ALL TO authenticated_users
    USING (has_role('admin') OR has_role('risk_manager'));

-- RLS Policies for Best Execution (Compliance officers can see all)
CREATE POLICY best_execution_policy ON best_execution_analysis
    FOR ALL TO authenticated_users
    USING (has_role('admin') OR has_role('compliance_officer') OR has_role('trader'));

-- RLS Policy: Users can only see their own audit events, unless they're admin/compliance
CREATE POLICY audit_events_user_policy ON transaction_audit_events
    FOR SELECT
    USING (
        actor = current_user
        OR has_role('admin')
        OR has_role('compliance_officer')
        OR has_role('risk_manager')
    );

-- RLS Policy: Only system can INSERT audit events (prevents tampering)
CREATE POLICY audit_events_insert_policy ON transaction_audit_events
    FOR INSERT
    WITH CHECK (has_role('admin') OR has_role('system'));

-- RLS Policies for MFA tables (if they exist)
DO $$
BEGIN
    IF EXISTS (SELECT 1 FROM pg_tables WHERE tablename = 'mfa_config') THEN
        DROP POLICY IF EXISTS mfa_config_self_access ON mfa_config;
        DROP POLICY IF EXISTS mfa_backup_codes_self_access ON mfa_backup_codes;
        DROP POLICY IF EXISTS mfa_verification_log_self_access ON mfa_verification_log;
        DROP POLICY IF EXISTS mfa_enrollment_sessions_self_access ON mfa_enrollment_sessions;

        CREATE POLICY mfa_config_self_access ON mfa_config
            FOR ALL TO foxhunt_user
            USING (user_id = current_setting('app.current_user_id')::uuid OR
                   current_setting('app.current_user_role') = 'admin');

        CREATE POLICY mfa_backup_codes_self_access ON mfa_backup_codes
            FOR ALL TO foxhunt_user
            USING (user_id = current_setting('app.current_user_id')::uuid OR
                   current_setting('app.current_user_role') = 'admin');

        CREATE POLICY mfa_verification_log_self_access ON mfa_verification_log
            FOR SELECT TO foxhunt_user
            USING (user_id = current_setting('app.current_user_id')::uuid OR
                   current_setting('app.current_user_role') = 'admin');

        CREATE POLICY mfa_enrollment_sessions_self_access ON mfa_enrollment_sessions
            FOR ALL TO foxhunt_user
            USING (user_id = current_setting('app.current_user_id')::uuid);

        RAISE NOTICE 'Created RLS policies for MFA tables';
    END IF;
END
$$;

-- ============================================================================
-- STEP 5: GRANT TABLE PERMISSIONS
-- ============================================================================

-- Grants for transaction audit events (from 020_transaction_audit_events.sql)
GRANT SELECT ON transaction_audit_events TO authenticated_users;
GRANT INSERT ON transaction_audit_events TO authenticated_users;
REVOKE UPDATE, DELETE ON transaction_audit_events FROM authenticated_users;
REVOKE UPDATE, DELETE ON transaction_audit_events FROM PUBLIC;

-- Grants for compliance audit trails (from 010_compliance_audit_trails.sql)
GRANT SELECT, INSERT ON sox_trade_audit TO authenticated_users;
GRANT SELECT, INSERT ON mifid_transaction_report TO authenticated_users;
GRANT SELECT, INSERT ON position_limits_audit TO authenticated_users;
GRANT SELECT, INSERT ON kill_switch_audit TO authenticated_users;
GRANT SELECT, INSERT ON best_execution_analysis TO authenticated_users;

-- Grants for compliance rules (from 011_compliance_rules_dynamic.sql)
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;

-- Grants for MFA tables (from 017_mfa_totp_implementation.sql)
DO $$
BEGIN
    IF EXISTS (SELECT 1 FROM pg_tables WHERE tablename = 'mfa_config') THEN
        GRANT SELECT, INSERT, UPDATE ON mfa_config TO foxhunt_user;
        GRANT SELECT, INSERT, UPDATE ON mfa_backup_codes TO foxhunt_user;
        GRANT SELECT, INSERT ON mfa_verification_log TO foxhunt_user;
        GRANT SELECT, INSERT, UPDATE, DELETE ON mfa_enrollment_sessions TO foxhunt_user;
        RAISE NOTICE 'Granted permissions on MFA tables to foxhunt_user';
    END IF;
END
$$;

-- ============================================================================
-- STEP 6: GRANT FUNCTION EXECUTE PERMISSIONS
-- ============================================================================

-- Grants for transaction audit functions (from 020_transaction_audit_events.sql)
DO $$
BEGIN
    -- Grant execute on functions that exist
    IF EXISTS (SELECT 1 FROM pg_proc WHERE proname = 'verify_audit_event_integrity') THEN
        GRANT EXECUTE ON FUNCTION verify_audit_event_integrity TO authenticated_users;
        RAISE NOTICE 'Granted EXECUTE on verify_audit_event_integrity';
    END IF;

    IF EXISTS (SELECT 1 FROM pg_proc WHERE proname = 'query_audit_events') THEN
        GRANT EXECUTE ON FUNCTION query_audit_events TO authenticated_users;
        RAISE NOTICE 'Granted EXECUTE on query_audit_events';
    ELSE
        RAISE NOTICE 'Function query_audit_events does not exist, skipping grant';
    END IF;

    IF EXISTS (SELECT 1 FROM pg_proc WHERE proname = 'get_audit_event_statistics') THEN
        GRANT EXECUTE ON FUNCTION get_audit_event_statistics TO authenticated_users;
        RAISE NOTICE 'Granted EXECUTE on get_audit_event_statistics';
    END IF;
END
$$;

-- Grants for compliance audit functions (from 010_compliance_audit_trails.sql)
GRANT EXECUTE ON FUNCTION log_sox_trade_activity TO authenticated_users;
GRANT EXECUTE ON FUNCTION create_mifid_transaction_report TO authenticated_users;
GRANT EXECUTE ON FUNCTION check_position_limits TO authenticated_users;
GRANT EXECUTE ON FUNCTION activate_kill_switch TO authenticated_users;
GRANT EXECUTE ON FUNCTION analyze_best_execution TO authenticated_users;

-- Grants for compliance rule functions (from 011_compliance_rules_dynamic.sql)
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;

-- ============================================================================
-- STEP 7: PRODUCTION SECURITY HARDENING
-- ============================================================================

-- Revoke public access to sensitive tables
REVOKE ALL ON sox_trade_audit FROM PUBLIC;
REVOKE ALL ON mifid_transaction_report FROM PUBLIC;
REVOKE ALL ON position_limits_audit FROM PUBLIC;
REVOKE ALL ON kill_switch_audit FROM PUBLIC;
REVOKE ALL ON best_execution_analysis FROM PUBLIC;
REVOKE ALL ON transaction_audit_events FROM PUBLIC;

-- Revoke public access to MFA tables if they exist
DO $$
BEGIN
    IF EXISTS (SELECT 1 FROM pg_tables WHERE tablename = 'mfa_config') THEN
        REVOKE ALL ON mfa_config FROM PUBLIC;
        REVOKE ALL ON mfa_backup_codes FROM PUBLIC;
        REVOKE ALL ON mfa_verification_log FROM PUBLIC;
        REVOKE ALL ON mfa_enrollment_sessions FROM PUBLIC;
        RAISE NOTICE 'Revoked public access from MFA tables';
    END IF;
END
$$;

-- ============================================================================
-- VERIFICATION QUERIES
-- ============================================================================

-- List all roles
SELECT 'ROLES CREATED:' as status;
SELECT rolname, rolcanlogin
FROM pg_roles
WHERE rolname IN ('authenticated_users', 'foxhunt_user', 'admin', 'compliance_officer', 'risk_manager', 'trader', 'system')
ORDER BY rolname;

-- List tables with RLS enabled
SELECT 'TABLES WITH RLS ENABLED:' as status;
SELECT schemaname, tablename, rowsecurity
FROM pg_tables
WHERE schemaname = 'public'
  AND rowsecurity = true
ORDER BY tablename;

-- List RLS policies
SELECT 'RLS POLICIES CREATED:' as status;
SELECT schemaname, tablename, policyname, permissive, roles, cmd
FROM pg_policies
WHERE schemaname = 'public'
ORDER BY tablename, policyname;

-- ============================================================================
-- PRODUCTION DEPLOYMENT NOTES
-- ============================================================================

-- IMPORTANT: Before deploying to production:
--
-- 1. Change foxhunt_user password:
--    ALTER ROLE foxhunt_user WITH PASSWORD 'strong_production_password';
--
-- 2. Create user-specific accounts with appropriate role grants:
--    CREATE USER prod_admin LOGIN PASSWORD 'secure_password';
--    GRANT admin TO prod_admin;
--    GRANT authenticated_users TO prod_admin;
--
-- 3. Set up SSL/TLS for database connections
--
-- 4. Configure connection pooling with appropriate limits
--
-- 5. Set up database backup and recovery procedures
--
-- 6. Enable audit logging in postgresql.conf:
--    log_statement = 'all'
--    log_connections = on
--    log_disconnections = on
--
-- 7. Configure pg_hba.conf for secure authentication
--
-- 8. Set up monitoring for failed login attempts
--
-- ============================================================================
-- END OF MIGRATION
-- ============================================================================
