-- ================================================================================================
-- Authentication & Authorization Schema Test Suite (Migration 015)
-- PostgreSQL 16.10 + TimescaleDB 2.22.1
-- Tests RBAC, JWT revocation, rate limiting, and security features
-- ================================================================================================

BEGIN;

-- ================================================================================================
-- TEST 1: Authentication Tables Existence
-- Verify all auth-related tables are created
-- ================================================================================================
DO $$
DECLARE
    v_required_tables TEXT[] := ARRAY[
        'users',
        'roles',
        'permissions',
        'user_roles',
        'role_permissions',
        'api_keys',
        'jwt_revocations',
        'rate_limits',
        'auth_sessions',
        'mfa_tokens',
        'password_history'
    ];
    v_table TEXT;
    v_missing_tables TEXT[] := ARRAY[]::TEXT[];
    v_found_count INTEGER := 0;
BEGIN
    FOREACH v_table IN ARRAY v_required_tables
    LOOP
        IF EXISTS (
            SELECT 1 FROM information_schema.tables
            WHERE table_schema = 'public'
              AND table_name = v_table
        ) THEN
            v_found_count := v_found_count + 1;
        ELSE
            v_missing_tables := array_append(v_missing_tables, v_table);
        END IF;
    END LOOP;

    IF array_length(v_missing_tables, 1) IS NULL THEN
        RAISE NOTICE 'TEST 1 PASS: All % auth tables exist', array_length(v_required_tables, 1);
    ELSE
        RAISE WARNING 'TEST 1 WARNING: Missing tables (% of %): %', 
            array_length(v_missing_tables, 1),
            array_length(v_required_tables, 1),
            array_to_string(v_missing_tables, ', ');
    END IF;
END $$;

-- ================================================================================================
-- TEST 2: User Creation and Validation
-- Test user table constraints and validation
-- ================================================================================================
DO $$
DECLARE
    v_user_id UUID := uuid_generate_v4();
    v_password_hash VARCHAR(255) := encode(sha256('SecureP@ssw0rd'::bytea), 'hex');
BEGIN
    -- Insert valid user
    INSERT INTO users (
        id, username, email, password_hash, is_active, is_verified
    ) VALUES (
        v_user_id, 'test_user_auth', 'test@example.com', v_password_hash, true, true
    );

    RAISE NOTICE 'TEST 2 PASS: User created successfully';

    -- Test duplicate username constraint
    BEGIN
        INSERT INTO users (
            username, email, password_hash
        ) VALUES (
            'test_user_auth', 'another@example.com', v_password_hash
        );
        RAISE EXCEPTION 'TEST 2 FAIL: Should have rejected duplicate username';
    EXCEPTION
        WHEN unique_violation THEN
            RAISE NOTICE 'TEST 2 PASS: Correctly rejected duplicate username';
    END;

    -- Test duplicate email constraint
    BEGIN
        INSERT INTO users (
            username, email, password_hash
        ) VALUES (
            'another_user', 'test@example.com', v_password_hash
        );
        RAISE EXCEPTION 'TEST 2 FAIL: Should have rejected duplicate email';
    EXCEPTION
        WHEN unique_violation THEN
            RAISE NOTICE 'TEST 2 PASS: Correctly rejected duplicate email';
    END;
END $$;

-- ================================================================================================
-- TEST 3: RBAC - Role and Permission Management
-- Test role-based access control setup
-- ================================================================================================
DO $$
DECLARE
    v_role_id UUID := uuid_generate_v4();
    v_perm_id UUID := uuid_generate_v4();
    v_user_id UUID;
BEGIN
    -- Create role
    INSERT INTO roles (id, name, description)
    VALUES (v_role_id, 'test_trader', 'Test trading role');

    -- Create permission
    INSERT INTO permissions (id, name, resource, action, description)
    VALUES (v_perm_id, 'trade:execute', 'orders', 'execute', 'Execute trading orders');

    -- Link permission to role
    INSERT INTO role_permissions (role_id, permission_id)
    VALUES (v_role_id, v_perm_id);

    -- Get test user
    SELECT id INTO v_user_id FROM users WHERE username = 'test_user_auth';

    -- Assign role to user
    INSERT INTO user_roles (user_id, role_id)
    VALUES (v_user_id, v_role_id);

    -- Verify RBAC chain
    IF EXISTS (
        SELECT 1
        FROM users u
        JOIN user_roles ur ON u.id = ur.user_id
        JOIN roles r ON ur.role_id = r.id
        JOIN role_permissions rp ON r.id = rp.role_id
        JOIN permissions p ON rp.permission_id = p.id
        WHERE u.id = v_user_id
          AND p.name = 'trade:execute'
    ) THEN
        RAISE NOTICE 'TEST 3 PASS: RBAC chain (user -> role -> permission) validated';
    ELSE
        RAISE EXCEPTION 'TEST 3 FAIL: RBAC chain validation failed';
    END IF;
END $$;

-- ================================================================================================
-- TEST 4: API Key Management
-- Test API key generation and validation
-- ================================================================================================
DO $$
DECLARE
    v_user_id UUID;
    v_api_key_id UUID := uuid_generate_v4();
    v_key_hash VARCHAR(255) := encode(sha256('test_api_key_12345'::bytea), 'hex');
BEGIN
    SELECT id INTO v_user_id FROM users WHERE username = 'test_user_auth';

    -- Create API key
    INSERT INTO api_keys (
        id, user_id, key_hash, name, expires_at
    ) VALUES (
        v_api_key_id, v_user_id, v_key_hash, 'Test API Key',
        NOW() + INTERVAL '30 days'
    );

    -- Verify API key
    IF EXISTS (
        SELECT 1 FROM api_keys
        WHERE id = v_api_key_id
          AND is_active = true
          AND expires_at > NOW()
    ) THEN
        RAISE NOTICE 'TEST 4 PASS: API key created and validated';
    ELSE
        RAISE EXCEPTION 'TEST 4 FAIL: API key validation failed';
    END IF;

    -- Test revocation
    UPDATE api_keys SET is_active = false WHERE id = v_api_key_id;

    IF EXISTS (
        SELECT 1 FROM api_keys
        WHERE id = v_api_key_id AND is_active = false
    ) THEN
        RAISE NOTICE 'TEST 4 PASS: API key revocation successful';
    ELSE
        RAISE EXCEPTION 'TEST 4 FAIL: API key revocation failed';
    END IF;
END $$;

-- ================================================================================================
-- TEST 5: JWT Revocation System
-- Test JWT token revocation
-- ================================================================================================
DO $$
DECLARE
    v_jti UUID := uuid_generate_v4();
    v_user_id UUID;
BEGIN
    SELECT id INTO v_user_id FROM users WHERE username = 'test_user_auth';

    -- Revoke JWT token
    INSERT INTO jwt_revocations (
        jti, user_id, expires_at, reason
    ) VALUES (
        v_jti, v_user_id, NOW() + INTERVAL '1 hour', 'User logout'
    );

    -- Verify revocation
    IF EXISTS (
        SELECT 1 FROM jwt_revocations
        WHERE jti = v_jti
          AND expires_at > NOW()
    ) THEN
        RAISE NOTICE 'TEST 5 PASS: JWT revocation recorded';
    ELSE
        RAISE EXCEPTION 'TEST 5 FAIL: JWT revocation failed';
    END IF;

    -- Test duplicate revocation (should fail)
    BEGIN
        INSERT INTO jwt_revocations (jti, user_id, expires_at)
        VALUES (v_jti, v_user_id, NOW() + INTERVAL '1 hour');
        RAISE EXCEPTION 'TEST 5 FAIL: Should have rejected duplicate JTI';
    EXCEPTION
        WHEN unique_violation THEN
            RAISE NOTICE 'TEST 5 PASS: Correctly rejected duplicate JWT revocation';
    END;
END $$;

-- ================================================================================================
-- TEST 6: Rate Limiting
-- Test rate limit tracking
-- ================================================================================================
DO $$
DECLARE
    v_user_id UUID;
    v_endpoint TEXT := '/api/v1/orders';
    v_window_start BIGINT := EXTRACT(EPOCH FROM NOW())::BIGINT;
    v_request_count INTEGER;
BEGIN
    SELECT id INTO v_user_id FROM users WHERE username = 'test_user_auth';

    -- Simulate multiple requests
    FOR i IN 1..5 LOOP
        INSERT INTO rate_limits (
            user_id, endpoint, window_start, request_count
        ) VALUES (
            v_user_id, v_endpoint, v_window_start, 1
        )
        ON CONFLICT (user_id, endpoint, window_start)
        DO UPDATE SET request_count = rate_limits.request_count + 1;
    END LOOP;

    -- Check rate limit counter
    SELECT request_count INTO v_request_count
    FROM rate_limits
    WHERE user_id = v_user_id
      AND endpoint = v_endpoint
      AND window_start = v_window_start;

    IF v_request_count = 5 THEN
        RAISE NOTICE 'TEST 6 PASS: Rate limiting counter accurate (% requests)', v_request_count;
    ELSE
        RAISE EXCEPTION 'TEST 6 FAIL: Expected 5 requests, found %', v_request_count;
    END IF;

    -- Test rate limit breach detection
    IF v_request_count > 3 THEN
        RAISE NOTICE 'TEST 6 INFO: Rate limit breach detected (limit: 3, actual: %)', v_request_count;
    END IF;
END $$;

-- ================================================================================================
-- TEST 7: MFA Token Management
-- Test multi-factor authentication tokens
-- ================================================================================================
DO $$
DECLARE
    v_user_id UUID;
    v_token_id UUID := uuid_generate_v4();
    v_secret VARCHAR(255) := encode(gen_random_bytes(20), 'base32');
BEGIN
    SELECT id INTO v_user_id FROM users WHERE username = 'test_user_auth';

    -- Create MFA token
    INSERT INTO mfa_tokens (
        id, user_id, token_type, secret, is_verified
    ) VALUES (
        v_token_id, v_user_id, 'totp', v_secret, true
    );

    -- Verify MFA token
    IF EXISTS (
        SELECT 1 FROM mfa_tokens
        WHERE id = v_token_id
          AND user_id = v_user_id
          AND is_verified = true
    ) THEN
        RAISE NOTICE 'TEST 7 PASS: MFA token created and verified';
    ELSE
        RAISE EXCEPTION 'TEST 7 FAIL: MFA token verification failed';
    END IF;

    -- Test MFA requirement
    UPDATE users SET mfa_enabled = true WHERE id = v_user_id;

    IF EXISTS (
        SELECT 1 FROM users u
        JOIN mfa_tokens m ON u.id = m.user_id
        WHERE u.id = v_user_id
          AND u.mfa_enabled = true
          AND m.is_verified = true
    ) THEN
        RAISE NOTICE 'TEST 7 PASS: MFA requirement enforced';
    ELSE
        RAISE EXCEPTION 'TEST 7 FAIL: MFA enforcement failed';
    END IF;
END $$;

-- ================================================================================================
-- TEST 8: Password History
-- Test password reuse prevention
-- ================================================================================================
DO $$
DECLARE
    v_user_id UUID;
    v_old_hash VARCHAR(255) := encode(sha256('OldP@ssw0rd'::bytea), 'hex');
    v_new_hash VARCHAR(255) := encode(sha256('NewP@ssw0rd'::bytea), 'hex');
BEGIN
    SELECT id INTO v_user_id FROM users WHERE username = 'test_user_auth';

    -- Store old password in history
    INSERT INTO password_history (user_id, password_hash)
    VALUES (v_user_id, v_old_hash);

    -- Verify history stored
    IF EXISTS (
        SELECT 1 FROM password_history
        WHERE user_id = v_user_id
          AND password_hash = v_old_hash
    ) THEN
        RAISE NOTICE 'TEST 8 PASS: Password history recorded';
    ELSE
        RAISE EXCEPTION 'TEST 8 FAIL: Password history storage failed';
    END IF;

    -- Test password reuse check
    IF EXISTS (
        SELECT 1 FROM password_history
        WHERE user_id = v_user_id
          AND password_hash = v_new_hash
    ) THEN
        RAISE EXCEPTION 'TEST 8 FAIL: Should prevent password reuse';
    ELSE
        RAISE NOTICE 'TEST 8 PASS: Password reuse prevention validated';
    END IF;
END $$;

-- ================================================================================================
-- TEST 9: Session Management
-- Test authentication session lifecycle
-- ================================================================================================
DO $$
DECLARE
    v_user_id UUID;
    v_session_id UUID := uuid_generate_v4();
    v_token VARCHAR(255) := encode(gen_random_bytes(32), 'hex');
BEGIN
    SELECT id INTO v_user_id FROM users WHERE username = 'test_user_auth';

    -- Create session
    INSERT INTO auth_sessions (
        id, user_id, token_hash, expires_at, ip_address, user_agent
    ) VALUES (
        v_session_id, v_user_id, encode(sha256(v_token::bytea), 'hex'),
        NOW() + INTERVAL '24 hours', '192.168.1.100', 'Test User Agent'
    );

    -- Verify active session
    IF EXISTS (
        SELECT 1 FROM auth_sessions
        WHERE id = v_session_id
          AND user_id = v_user_id
          AND expires_at > NOW()
    ) THEN
        RAISE NOTICE 'TEST 9 PASS: Session created successfully';
    ELSE
        RAISE EXCEPTION 'TEST 9 FAIL: Session creation failed';
    END IF;

    -- Test session invalidation
    UPDATE auth_sessions SET expires_at = NOW() - INTERVAL '1 hour'
    WHERE id = v_session_id;

    IF NOT EXISTS (
        SELECT 1 FROM auth_sessions
        WHERE id = v_session_id AND expires_at > NOW()
    ) THEN
        RAISE NOTICE 'TEST 9 PASS: Session invalidation successful';
    ELSE
        RAISE EXCEPTION 'TEST 9 FAIL: Session invalidation failed';
    END IF;
END $$;

-- ================================================================================================
-- TEST 10: Security Audit Trail
-- Verify authentication events are logged
-- ================================================================================================
DO $$
DECLARE
    v_user_id UUID;
    v_current_ns BIGINT := EXTRACT(EPOCH FROM NOW())::BIGINT * 1000000000;
    v_auth_events INTEGER;
BEGIN
    SELECT id INTO v_user_id FROM users WHERE username = 'test_user_auth';

    -- Log authentication events
    INSERT INTO audit_events (
        correlation_id, event_timestamp, event_type, severity, component,
        user_id, event_data, event_hash, node_id, process_id
    ) VALUES
    (uuid_generate_v4(), v_current_ns, 'authentication_attempt', 'info', 'authentication',
     v_user_id::text, '{"method": "password", "ip": "192.168.1.100"}'::jsonb,
     encode(sha256('auth1'::bytea), 'hex'), 'node-01', 12345),
    (uuid_generate_v4(), v_current_ns + 1000000, 'authentication_success', 'info', 'authentication',
     v_user_id::text, '{"method": "password", "ip": "192.168.1.100"}'::jsonb,
     encode(sha256('auth2'::bytea), 'hex'), 'node-01', 12345);

    -- Count auth events
    SELECT COUNT(*) INTO v_auth_events
    FROM audit_events
    WHERE user_id = v_user_id::text
      AND event_type IN ('authentication_attempt', 'authentication_success');

    IF v_auth_events >= 2 THEN
        RAISE NOTICE 'TEST 10 PASS: Authentication audit trail complete (% events logged)', v_auth_events;
    ELSE
        RAISE EXCEPTION 'TEST 10 FAIL: Authentication audit trail incomplete';
    END IF;
END $$;

-- ================================================================================================
-- Cleanup: Rollback all test data
-- ================================================================================================
ROLLBACK;

-- Test Summary
SELECT 'Authentication & Authorization Test Suite Completed - Review NOTICE/WARNING messages above' AS test_summary;
