-- ================================================================================================
-- Schema Validation Test Suite
-- PostgreSQL 16.10 + TimescaleDB 2.22.1
-- Validates all required tables, constraints, indexes, and types exist
-- ================================================================================================

BEGIN;

-- ================================================================================================
-- TEST 1: Core Tables Existence
-- Verify all critical tables are created
-- ================================================================================================
DO $$
DECLARE
    v_required_tables TEXT[] := ARRAY[
        'trading_events',
        'risk_events',
        'audit_events',
        'orders',
        'executions',
        'positions',
        'accounts',
        'users',
        'roles',
        'permissions',
        'user_roles',
        'role_permissions',
        'api_keys',
        'jwt_revocations',
        'rate_limits',
        'compliance_reports',
        'market_data_raw',
        'market_data_aggregated',
        'config_parameters',
        'config_history',
        'symbols',
        'symbol_metadata'
    ];
    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 % required 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: Required Enums Validation
-- Verify all enum types are defined
-- ================================================================================================
DO $$
DECLARE
    v_required_enums TEXT[] := ARRAY[
        'trading_event_type',
        'order_side',
        'order_type',
        'time_in_force',
        'order_status',
        'execution_type',
        'risk_event_type',
        'risk_severity',
        'risk_metric_type',
        'audit_event_type',
        'audit_severity',
        'system_component',
        'account_type',
        'position_side',
        'execution_status'
    ];
    v_enum TEXT;
    v_missing_enums TEXT[] := ARRAY[]::TEXT[];
BEGIN
    FOREACH v_enum IN ARRAY v_required_enums
    LOOP
        IF NOT EXISTS (
            SELECT 1 FROM pg_type
            WHERE typname = v_enum
              AND typtype = 'e'
        ) THEN
            v_missing_enums := array_append(v_missing_enums, v_enum);
        END IF;
    END LOOP;

    IF array_length(v_missing_enums, 1) IS NULL THEN
        RAISE NOTICE 'TEST 2 PASS: All % required enum types exist', array_length(v_required_enums, 1);
    ELSE
        RAISE WARNING 'TEST 2 WARNING: Missing enum types: %', array_to_string(v_missing_enums, ', ');
    END IF;
END $$;

-- ================================================================================================
-- TEST 3: Primary Key Constraints
-- Verify all tables have primary keys
-- ================================================================================================
DO $$
DECLARE
    v_table_record RECORD;
    v_tables_without_pk TEXT[] := ARRAY[]::TEXT[];
    v_checked_count INTEGER := 0;
BEGIN
    FOR v_table_record IN
        SELECT table_name
        FROM information_schema.tables
        WHERE table_schema = 'public'
          AND table_type = 'BASE TABLE'
          AND table_name NOT LIKE '%_old'
          AND table_name NOT LIKE '%_backup'
    LOOP
        v_checked_count := v_checked_count + 1;
        
        IF NOT EXISTS (
            SELECT 1
            FROM information_schema.table_constraints
            WHERE table_schema = 'public'
              AND table_name = v_table_record.table_name
              AND constraint_type = 'PRIMARY KEY'
        ) THEN
            v_tables_without_pk := array_append(v_tables_without_pk, v_table_record.table_name);
        END IF;
    END LOOP;

    IF array_length(v_tables_without_pk, 1) IS NULL THEN
        RAISE NOTICE 'TEST 3 PASS: All % tables have primary key constraints', v_checked_count;
    ELSE
        RAISE WARNING 'TEST 3 WARNING: Tables without primary keys: %', array_to_string(v_tables_without_pk, ', ');
    END IF;
END $$;

-- ================================================================================================
-- TEST 4: Foreign Key Constraints
-- Verify critical foreign key relationships
-- ================================================================================================
DO $$
DECLARE
    v_required_fks TEXT[][] := ARRAY[
        ARRAY['orders', 'account_id', 'accounts'],
        ARRAY['executions', 'order_id', 'orders'],
        ARRAY['positions', 'account_id', 'accounts'],
        ARRAY['user_roles', 'user_id', 'users'],
        ARRAY['user_roles', 'role_id', 'roles'],
        ARRAY['role_permissions', 'role_id', 'roles'],
        ARRAY['role_permissions', 'permission_id', 'permissions'],
        ARRAY['api_keys', 'user_id', 'users']
    ];
    v_fk TEXT[];
    v_missing_fks TEXT[] := ARRAY[]::TEXT[];
    v_fk_desc TEXT;
BEGIN
    FOREACH v_fk SLICE 1 IN ARRAY v_required_fks
    LOOP
        v_fk_desc := v_fk[1] || '.' || v_fk[2] || ' -> ' || v_fk[3];
        
        IF NOT EXISTS (
            SELECT 1
            FROM information_schema.table_constraints tc
            JOIN information_schema.key_column_usage kcu
                ON tc.constraint_name = kcu.constraint_name
                AND tc.table_schema = kcu.table_schema
            JOIN information_schema.constraint_column_usage ccu
                ON ccu.constraint_name = tc.constraint_name
                AND ccu.table_schema = tc.table_schema
            WHERE tc.constraint_type = 'FOREIGN KEY'
              AND tc.table_name = v_fk[1]
              AND kcu.column_name = v_fk[2]
              AND ccu.table_name = v_fk[3]
        ) THEN
            -- Check if table and column exist before marking as missing FK
            IF EXISTS (SELECT 1 FROM information_schema.tables WHERE table_name = v_fk[1])
               AND EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = v_fk[1] AND column_name = v_fk[2])
               AND EXISTS (SELECT 1 FROM information_schema.tables WHERE table_name = v_fk[3])
            THEN
                v_missing_fks := array_append(v_missing_fks, v_fk_desc);
            END IF;
        END IF;
    END LOOP;

    IF array_length(v_missing_fks, 1) IS NULL THEN
        RAISE NOTICE 'TEST 4 PASS: All critical foreign key relationships verified';
    ELSE
        RAISE WARNING 'TEST 4 WARNING: Missing foreign keys: %', array_to_string(v_missing_fks, ', ');
    END IF;
END $$;

-- ================================================================================================
-- TEST 5: Index Coverage - Performance Indexes
-- Verify critical indexes exist for query performance
-- ================================================================================================
DO $$
DECLARE
    v_required_indexes TEXT[][] := ARRAY[
        ARRAY['trading_events', 'symbol'],
        ARRAY['trading_events', 'event_type'],
        ARRAY['trading_events', 'correlation_id'],
        ARRAY['risk_events', 'symbol'],
        ARRAY['risk_events', 'account_id'],
        ARRAY['risk_events', 'severity'],
        ARRAY['audit_events', 'user_id'],
        ARRAY['audit_events', 'component'],
        ARRAY['orders', 'symbol'],
        ARRAY['orders', 'account_id'],
        ARRAY['orders', 'status'],
        ARRAY['executions', 'order_id'],
        ARRAY['positions', 'account_id'],
        ARRAY['positions', 'symbol']
    ];
    v_idx TEXT[];
    v_missing_indexes TEXT[] := ARRAY[]::TEXT[];
    v_idx_desc TEXT;
BEGIN
    FOREACH v_idx SLICE 1 IN ARRAY v_required_indexes
    LOOP
        v_idx_desc := v_idx[1] || '.' || v_idx[2];
        
        IF NOT EXISTS (
            SELECT 1
            FROM pg_indexes
            WHERE tablename = v_idx[1]
              AND indexdef LIKE '%' || v_idx[2] || '%'
        ) THEN
            -- Check if table exists before marking as missing index
            IF EXISTS (SELECT 1 FROM information_schema.tables WHERE table_name = v_idx[1]) THEN
                v_missing_indexes := array_append(v_missing_indexes, v_idx_desc);
            END IF;
        END IF;
    END LOOP;

    IF array_length(v_missing_indexes, 1) IS NULL THEN
        RAISE NOTICE 'TEST 5 PASS: All critical performance indexes exist';
    ELSE
        RAISE WARNING 'TEST 5 WARNING: Missing indexes: %', array_to_string(v_missing_indexes, ', ');
    END IF;
END $$;

-- ================================================================================================
-- TEST 6: NOT NULL Constraints - Critical Fields
-- Verify critical fields have NOT NULL constraints
-- ================================================================================================
DO $$
DECLARE
    v_required_not_nulls TEXT[][] := ARRAY[
        ARRAY['trading_events', 'event_timestamp'],
        ARRAY['trading_events', 'event_type'],
        ARRAY['trading_events', 'symbol'],
        ARRAY['risk_events', 'event_timestamp'],
        ARRAY['risk_events', 'severity'],
        ARRAY['audit_events', 'event_timestamp'],
        ARRAY['audit_events', 'event_type'],
        ARRAY['orders', 'symbol'],
        ARRAY['orders', 'side'],
        ARRAY['orders', 'order_type'],
        ARRAY['accounts', 'id'],
        ARRAY['users', 'username'],
        ARRAY['users', 'email']
    ];
    v_nn TEXT[];
    v_missing_not_nulls TEXT[] := ARRAY[]::TEXT[];
    v_nn_desc TEXT;
BEGIN
    FOREACH v_nn SLICE 1 IN ARRAY v_required_not_nulls
    LOOP
        v_nn_desc := v_nn[1] || '.' || v_nn[2];
        
        IF NOT EXISTS (
            SELECT 1
            FROM information_schema.columns
            WHERE table_name = v_nn[1]
              AND column_name = v_nn[2]
              AND is_nullable = 'NO'
        ) THEN
            -- Check if column exists
            IF EXISTS (
                SELECT 1 FROM information_schema.columns
                WHERE table_name = v_nn[1] AND column_name = v_nn[2]
            ) THEN
                v_missing_not_nulls := array_append(v_missing_not_nulls, v_nn_desc);
            END IF;
        END IF;
    END LOOP;

    IF array_length(v_missing_not_nulls, 1) IS NULL THEN
        RAISE NOTICE 'TEST 6 PASS: All critical NOT NULL constraints exist';
    ELSE
        RAISE WARNING 'TEST 6 WARNING: Missing NOT NULL constraints: %', array_to_string(v_missing_not_nulls, ', ');
    END IF;
END $$;

-- ================================================================================================
-- TEST 7: CHECK Constraints - Data Integrity
-- Verify CHECK constraints for data validation
-- ================================================================================================
DO $$
DECLARE
    v_constraint_count INTEGER;
BEGIN
    -- Count CHECK constraints in critical tables
    SELECT COUNT(*)
    INTO v_constraint_count
    FROM information_schema.table_constraints
    WHERE constraint_type = 'CHECK'
      AND table_schema = 'public'
      AND table_name IN ('trading_events', 'risk_events', 'orders', 'executions', 'positions');

    IF v_constraint_count > 0 THEN
        RAISE NOTICE 'TEST 7 PASS: Found % CHECK constraints for data integrity', v_constraint_count;
    ELSE
        RAISE WARNING 'TEST 7 WARNING: No CHECK constraints found in critical tables';
    END IF;
END $$;

-- ================================================================================================
-- TEST 8: Unique Constraints - Duplicate Prevention
-- Verify unique constraints exist
-- ================================================================================================
DO $$
DECLARE
    v_unique_count INTEGER;
BEGIN
    SELECT COUNT(*)
    INTO v_unique_count
    FROM information_schema.table_constraints
    WHERE constraint_type = 'UNIQUE'
      AND table_schema = 'public';

    IF v_unique_count > 0 THEN
        RAISE NOTICE 'TEST 8 PASS: Found % UNIQUE constraints', v_unique_count;
    ELSE
        RAISE WARNING 'TEST 8 WARNING: No UNIQUE constraints found';
    END IF;
END $$;

-- ================================================================================================
-- TEST 9: Default Values - Auto-population
-- Verify critical defaults are set
-- ================================================================================================
DO $$
DECLARE
    v_defaults_with_defaults TEXT[] := ARRAY[]::TEXT[];
    v_col_record RECORD;
BEGIN
    FOR v_col_record IN
        SELECT table_name, column_name, column_default
        FROM information_schema.columns
        WHERE table_schema = 'public'
          AND (
              (column_name LIKE '%_id' AND column_default LIKE '%uuid_generate%')
              OR (column_name LIKE '%_timestamp' AND column_default IS NOT NULL)
              OR (column_name = 'created_at' AND column_default IS NOT NULL)
              OR (column_name = 'updated_at' AND column_default IS NOT NULL)
          )
    LOOP
        v_defaults_with_defaults := array_append(v_defaults_with_defaults, 
            v_col_record.table_name || '.' || v_col_record.column_name);
    END LOOP;

    IF array_length(v_defaults_with_defaults, 1) > 0 THEN
        RAISE NOTICE 'TEST 9 PASS: Found % columns with default values', array_length(v_defaults_with_defaults, 1);
    ELSE
        RAISE WARNING 'TEST 9 WARNING: No default values found on expected columns';
    END IF;
END $$;

-- ================================================================================================
-- TEST 10: Database Extensions
-- Verify required PostgreSQL extensions are installed
-- ================================================================================================
DO $$
DECLARE
    v_required_extensions TEXT[] := ARRAY[
        'uuid-ossp',
        'pgcrypto',
        'timescaledb'
    ];
    v_ext TEXT;
    v_missing_extensions TEXT[] := ARRAY[]::TEXT[];
    v_installed_extensions TEXT[] := ARRAY[]::TEXT[];
BEGIN
    FOREACH v_ext IN ARRAY v_required_extensions
    LOOP
        IF EXISTS (
            SELECT 1 FROM pg_extension WHERE extname = v_ext
        ) THEN
            v_installed_extensions := array_append(v_installed_extensions, v_ext);
        ELSE
            v_missing_extensions := array_append(v_missing_extensions, v_ext);
        END IF;
    END LOOP;

    IF array_length(v_missing_extensions, 1) IS NULL THEN
        RAISE NOTICE 'TEST 10 PASS: All required extensions installed: %', array_to_string(v_installed_extensions, ', ');
    ELSE
        RAISE WARNING 'TEST 10 WARNING: Missing extensions: %', array_to_string(v_missing_extensions, ', ');
    END IF;
END $$;

-- ================================================================================================
-- Cleanup: Rollback transaction
-- ================================================================================================
ROLLBACK;

-- Test Summary
SELECT 'Schema Validation Test Suite Completed - Review NOTICE/WARNING messages above' AS test_summary;
