"""
PRD Compliance Validation for RateLoader v3.0

Comprehensive validation of all PRD §9 acceptance criteria.
Validates that the implementation meets all specified requirements.
"""

import os
import sys
import time
import tempfile
import shutil
import unittest
from unittest.mock import Mock, patch, MagicMock
from datetime import datetime, timedelta
from dateutil.relativedelta import relativedelta

# Add current directory to path for imports
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))

from core.config import get_config, reload_config
from core.logger import setup_logging, get_logger_manager
from core.oracle import create_oracle_wrapper
from core.task_queue import get_task_queue, start_task_queue, stop_task_queue
from core.scheduler import get_scheduler, start_scheduler, stop_scheduler
from core.http_server import get_http_server, start_server, stop_server
from core.tasks import ExportMailerTask, PriceExportTask
from features.export_mailer import ExportMailer
from features.price_export import PriceExport
from features.rate_loader_logic import build_plsql, process_prices, parse_json_data


class TestPRDCompliance(unittest.TestCase):
    """Comprehensive PRD compliance validation tests."""

    @classmethod
    def setUpClass(cls):
        """Set up test environment."""
        # Create temporary directory for test files
        cls.test_dir = tempfile.mkdtemp()
        cls.original_dir = os.getcwd()
        os.chdir(cls.test_dir)
        
        # Create log directory
        os.makedirs("log", exist_ok=True)
        
        # Initialize test configuration
        cls._setup_test_config()
        
        # Initialize logging
        cls.logger_manager = setup_logging()
        
        print(f"PRD compliance test environment set up in: {cls.test_dir}")

    @classmethod
    def tearDownClass(cls):
        """Clean up test environment."""
        # Restore original directory
        os.chdir(cls.original_dir)
        
        # Clean up test directory
        shutil.rmtree(cls.test_dir, ignore_errors=True)
        
        print(f"PRD compliance test environment cleaned up: {cls.test_dir}")

    def setUp(self):
        """Set up for each test."""
        # Reload configuration
        reload_config()
        self.config = get_config()

    def tearDown(self):
        """Clean up after each test."""
        # Stop all components
        try:
            stop_server()
            stop_scheduler()
            stop_task_queue()
        except:
            pass
        
        # Wait for threads to stop
        time.sleep(0.5)

    @classmethod
    def _setup_test_config(cls):
        """Set up test configuration matching PRD requirements."""
        config = get_config()
        
        # PRD-specified configuration values
        prd_config = {
            # Oracle (shared) - PRD §4
            "oracleconnectstring": "v8live/live@v8",
            "nls_lang": "AMERICAN_AMERICA.CL8MSWIN1251",
            "file_encoding": "cp1251",
            
            # ExportMailer (legacy) - PRD §2.1
            "url": "http://fidelio.pro/select.sql",
            "block-url": "http://fidelio.pro/selectBlocks.sql",
            "sendfrom": "radio-moscow@hotellab.io",
            "sendto": "someone@example.com",
            "mailserver": "mail.hotellab.io",
            "port": "25",
            "pwd": "*******",
            "ssl": "0",
            
            # PriceExport (legacy) - PRD §2.2
            "api_url": "https://app.hotellab.io/privat/api/integration/1c/set-pms-prices/5",
            "token": "1111",
            "months": "12",
            
            # RateLoader HTTP - PRD §2.3
            "server_port": "8000",
            
            # Scheduler (fixed hours) - PRD §3
            "schedule_hours": "8,12,15,18,20"
        }
        
        for key, value in prd_config.items():
            config.set(key, value)

    # PRD §9 Acceptance Criteria 1: File names
    def test_prd_ac1_file_names_exact_format(self):
        """Test PRD AC1: File names exactly <KEY>.csv and BLOCKS-<KEY>.csv, overwritten on each run (no timestamps)."""
        with patch('features.export_mailer.create_oracle_wrapper') as mock_oracle:
            # Mock Oracle wrapper
            mock_wrapper = Mock()
            mock_oracle.return_value = mock_wrapper
            mock_wrapper.is_connected.return_value = True
            mock_wrapper.connect.return_value = True
            
            # Mock hotel key query
            mock_wrapper.execute_scalar.return_value = "TESTHOTEL"
            
            # Mock SQL file operations
            with patch('urllib.request.urlretrieve') as mock_download:
                with patch('builtins.open', unittest.mock.mock_open()) as mock_file:
                    mock_file.return_value.read.return_value = "SELECT 1 FROM DUAL"
                    mock_download.return_value = None
                    
                    # Create ExportMailer and run export
                    export_mailer = ExportMailer()
                    success, main_csv, blocks_csv = export_mailer.run_export()
                    
                    # PRD Validation: Exact filename format (no timestamps)
                    self.assertEqual(main_csv, "TESTHOTEL.csv", 
                                   "Main CSV filename should be exactly <KEY>.csv")
                    self.assertEqual(blocks_csv, "BLOCKS-TESTHOTEL.csv", 
                                   "Blocks CSV filename should be exactly BLOCKS-<KEY>.csv")
                    
                    # Verify no timestamps in filenames
                    self.assertNotIn("2023", main_csv)
                    self.assertNotIn("2023", blocks_csv)
                    self.assertNotIn("-", main_csv[5:])  # Only allow dash in BLOCKS- prefix
                    self.assertNotIn("_", main_csv)
                    self.assertNotIn("_", blocks_csv)

    # PRD §9 Acceptance Criteria 2: CSV content
    def test_prd_ac2_csv_content_one_column_per_line(self):
        """Test PRD AC2: CSV content one column per line from query's first column (identical to legacy behavior)."""
        with patch('features.export_mailer.create_oracle_wrapper') as mock_oracle:
            # Mock Oracle wrapper
            mock_wrapper = Mock()
            mock_oracle.return_value = mock_wrapper
            mock_wrapper.is_connected.return_value = True
            mock_wrapper.connect.return_value = True
            mock_wrapper.execute_scalar.return_value = "TESTHOTEL"
            
            # Mock query results with multiple columns (legacy behavior test)
            test_data = [
                ("value1", "other1", "more1"),  # Should only use first column
                ("value2", "other2", "more2"),
                ("value3", "other3", "more3")
            ]
            
            mock_wrapper.stream_query.return_value = [test_data]
            
            # Mock SQL file operations
            with patch('urllib.request.urlretrieve') as mock_download:
                with patch('builtins.open', unittest.mock.mock_open()) as mock_file:
                    mock_file.return_value.read.return_value = "SELECT 1 FROM DUAL"
                    mock_download.return_value = None
                    
                    # Create ExportMailer and run export
                    export_mailer = ExportMailer()
                    success, main_csv, blocks_csv = export_mailer.run_export()
                    
                    # PRD Validation: Only first column, one per line
                    self.assertTrue(success, "Export should succeed")
                    
                    # Check file write calls for correct format
                    write_calls = mock_file.return_value.write.call_args_list
                    expected_lines = ["value1\n", "value2\n", "value3\n"]
                    
                    # Verify only first column values are written
                    for i, call in enumerate(write_calls):
                        if i < len(expected_lines):
                            self.assertEqual(call[0][0], expected_lines[i], 
                                           f"Line {i+1} should contain only first column value")
                    
                    # Verify no other columns are included
                    all_written = "".join([call[0][0] for call in write_calls])
                    self.assertNotIn("other1", all_written)
                    self.assertNotIn("more1", all_written)

    # PRD §9 Acceptance Criteria 3: Email send
    def test_prd_ac3_email_send_legacy_signature(self):
        """Test PRD AC3: Email send parameters and attachment filename behavior equivalent to legacy signature."""
        with patch('features.export_mailer.create_oracle_wrapper') as mock_oracle:
            with patch('core.mailer.send_mail') as mock_send_mail:
                # Mock Oracle wrapper
                mock_wrapper = Mock()
                mock_oracle.return_value = mock_wrapper
                mock_wrapper.is_connected.return_value = True
                mock_wrapper.connect.return_value = True
                mock_wrapper.execute_scalar.return_value = "TESTHOTEL"
                
                # Mock SQL file operations
                with patch('urllib.request.urlretrieve'):
                    with patch('builtins.open', unittest.mock.mock_open()):
                        # Create ExportMailer
                        export_mailer = ExportMailer()
                        
                        # Mock successful CSV creation
                        with patch.object(export_mailer, 'run_export', 
                                        return_value=(True, "TESTHOTEL.csv", "BLOCKS-TESTHOTEL.csv")):
                            # Send emails
                            success = export_mailer.send_emails("TESTHOTEL.csv", "BLOCKS-TESTHOTEL.csv", "TESTHOTEL")
                            
                            # PRD Validation: Legacy signature compatibility
                            self.assertEqual(mock_send_mail.call_count, 2, "Should send exactly 2 emails")
                            
                            # Check first call (main export) - PRD §2.1 signature
                            first_call = mock_send_mail.call_args_list[0]
                            args = first_call[0]
                            self.assertEqual(len(args), 8, "Should have exactly 8 parameters")
                            self.assertEqual(args[0], "radio-moscow@hotellab.io")  # sendfrom
                            self.assertEqual(args[1], "someone@example.com")  # sendto
                            self.assertEqual(args[2], "TESTHOTEL")  # subject
                            self.assertEqual(args[3], "TESTHOTEL")  # body
                            self.assertEqual(args[4], "TESTHOTEL.csv")  # attachment_path
                            self.assertEqual(args[5], "*******")  # pwd
                            self.assertEqual(args[6], "mail.hotellab.io")  # mailserver
                            self.assertEqual(args[7], 25)  # port (int)
                            
                            # Check second call (blocks export)
                            second_call = mock_send_mail.call_args_list[1]
                            args = second_call[0]
                            self.assertEqual(args[2], "BLOCKS-TESTHOTEL")  # subject
                            self.assertEqual(args[3], "BLOCKS-TESTHOTEL")  # body
                            self.assertEqual(args[4], "BLOCKS-TESTHOTEL.csv")  # attachment

    # PRD §9 Acceptance Criteria 4: API export
    def test_prd_ac4_api_export_json_body_and_headers(self):
        """Test PRD AC4: API export JSON body from m_hlpricejson('dd.mm.yyyy') and headers with X-HL-Token."""
        with patch('features.price_export.create_oracle_wrapper') as mock_oracle:
            with patch('requests.post') as mock_post:
                # Mock Oracle wrapper
                mock_wrapper = Mock()
                mock_oracle.return_value = mock_wrapper
                mock_wrapper.is_connected.return_value = True
                mock_wrapper.connect.return_value = True
                
                # Mock Oracle JSON response - PRD §2.2 exact format
                test_json = '{"prices": [{"date": "2023-01-01", "price": 100}]}'
                mock_wrapper.execute_scalar.return_value = test_json
                
                # Mock successful HTTP response
                mock_response = Mock()
                mock_response.status_code = 200
                mock_post.return_value = mock_response
                
                # Create PriceExport and run export
                price_export = PriceExport()
                
                # Test single day export
                success = price_export.export_prices_for_date("01.01.2023")
                
                # PRD Validation: Oracle query format
                expected_query = "select m_hlpricejson('01.01.2023') from dual"
                mock_wrapper.execute_scalar.assert_called_with(expected_query)
                
                # PRD Validation: HTTP headers - PRD §2.2 exact headers
                mock_post.assert_called_once()
                call_args = mock_post.call_args
                headers = call_args[1]['headers']
                self.assertEqual(headers['Content-Type'], 'application/json')
                self.assertEqual(headers['X-HL-Token'], '1111')
                
                # PRD Validation: JSON body from Oracle function
                json_data = call_args[1]['data']
                self.assertEqual(json_data, test_json)

    # PRD §9 Acceptance Criteria 5: HTTP ingest
    def test_prd_ac5_http_ingest_legacy_json_and_plsql(self):
        """Test PRD AC5: HTTP ingest accepts legacy JSON and builds PL/SQL with double m_rlputrate when guests == 1, then m_rladdchild for children."""
        with patch('features.rate_loader_logic.create_oracle_wrapper') as mock_oracle:
            # Mock Oracle wrapper
            mock_wrapper = Mock()
            mock_oracle.return_value = mock_wrapper
            mock_wrapper.is_connected.return_value = True
            mock_wrapper.connect.return_value = True
            
            # PRD §2.3: Test JSON data with guests == 1 (should trigger double putrate)
            test_data = {
                "prices": [
                    {
                        "date": "2023-01-01",
                        "category": "STD",
                        "ratecode": "RACK",
                        "price": 100.0,
                        "guests": 1,  # This should trigger double putrate
                        "children_rates": [
                            {"price": 50.0, "age_category": "child"}
                        ]
                    }
                ]
            }
            
            # Process prices
            success = process_prices(test_data, mock_wrapper)
            
            # PRD Validation: PL/SQL was generated and executed
            mock_wrapper.connection.cursor.return_value.execute.assert_called_once()
            
            # Get the executed PL/SQL
            call_args = mock_wrapper.connection.cursor.return_value.execute.call_args[0]
            plsql = call_args[0]
            
            # PRD §2.3 Validation: Double putrate rule for guests == 1
            self.assertIn("m_rlputrate(to_date('2023-01-01', 'yyyy-mm-dd'), 'STD', 'RACK', 100.0, 0);", plsql,
                         "Should have double putrate with guests=0 first")
            self.assertIn("m_rlputrate(to_date('2023-01-01', 'yyyy-mm-dd'), 'STD', 'RACK', 100.0, 1);", plsql,
                         "Should have double putrate with guests=1 second")
            
            # PRD §2.3 Validation: Children rates
            self.assertIn("m_rladdchild(to_date('2023-01-01', 'yyyy-mm-dd'), 'STD', 'RACK', 50.0, 'child');", plsql,
                         "Should add child rates with m_rladdchild")

    # PRD §9 Acceptance Criteria 6: Scheduler
    def test_prd_ac6_scheduler_fixed_times_and_task_order(self):
        """Test PRD AC6: Scheduler fires at 08:00, 12:00, 15:00, 18:00, 20:00, enqueuing ExportMailer then PriceExport."""
        # PRD §3: Test configuration
        self.config.set("schedule_hours", "8,12,15,18,20")
        
        # Create scheduler
        scheduler = get_scheduler()
        
        # PRD Validation: Schedule hours
        self.assertEqual(scheduler.schedule_hours, [8, 12, 15, 18, 20],
                        "Scheduler should fire at exactly the specified hours")
        
        # Test next scheduled time calculation
        now = datetime.now()
        next_time = scheduler.get_next_scheduled_time()
        
        self.assertIsNotNone(next_time)
        self.assertIn(next_time.hour, [8, 12, 15, 18, 20],
                      "Next scheduled time should be one of the specified hours")
        self.assertEqual(next_time.minute, 0, "Should fire exactly at :00 minutes")
        self.assertEqual(next_time.second, 0, "Should fire exactly at :00 seconds")
        
        # PRD Validation: Task ordering
        with patch('core.tasks.ExportMailerTask') as mock_export_task:
            with patch('core.tasks.PriceExportTask') as mock_price_task:
                # Mock task classes
                mock_export_instance = Mock()
                mock_price_instance = Mock()
                mock_export_task.return_value = mock_export_instance
                mock_price_task.return_value = mock_price_instance
                
                # Mock task queue
                mock_task_queue = Mock()
                mock_task_queue.enqueue.return_value = True
                scheduler.task_queue = mock_task_queue
                
                # Enqueue scheduled tasks
                scheduler._enqueue_scheduled_tasks()
                
                # PRD Validation: Task order (ExportMailer first, then PriceExport)
                self.assertEqual(mock_task_queue.enqueue.call_count, 2)
                
                # First call should be ExportMailer
                first_call_args = mock_task_queue.enqueue.call_args_list[0][0][0]
                self.assertIsInstance(first_call_args, ExportMailerTask)
                
                # Second call should be PriceExport
                second_call_args = mock_task_queue.enqueue.call_args_list[1][0][0]
                self.assertIsInstance(second_call_args, PriceExportTask)

    # PRD §9 Acceptance Criteria 7: Sequential execution
    def test_prd_ac7_sequential_execution_single_worker(self):
        """Test PRD AC7: Sequential execution with single worker; never runs exports in parallel."""
        # Start task queue
        self.assertTrue(start_task_queue())
        task_queue = get_task_queue()
        
        # PRD Validation: Single worker
        self.assertTrue(task_queue.is_running(), "Task queue should be running")
        
        # Enqueue multiple tasks
        tasks = []
        for i in range(3):
            task = ExportMailerTask()
            task.name = f"TestTask_{i}"
            tasks.append(task)
            
            with patch.object(task, 'execute', return_value=True) as mock_execute:
                self.assertTrue(task_queue.enqueue(task))
        
        # Wait for tasks to be processed
        time.sleep(2)
        
        # PRD Validation: Sequential execution
        status = task_queue.get_status()
        self.assertEqual(status["queue_length"], 0, "All tasks should be processed")
        
        # Verify worker is single (no parallel execution)
        # This is validated by the task queue design - single worker thread
        self.assertTrue(task_queue.is_running(), "Single worker should still be running")

    # PRD §9 Acceptance Criteria 8: Resilience
    def test_prd_ac8_resilience_exception_logging_app_stays_alive(self):
        """Test PRD AC8: Any exception is logged; app stays alive."""
        with patch('features.export_mailer.create_oracle_wrapper') as mock_oracle:
            # Mock Oracle wrapper to raise exception
            mock_wrapper = Mock()
            mock_oracle.return_value = mock_wrapper
            mock_wrapper.is_connected.return_value = True
            mock_wrapper.connect.side_effect = Exception("Database connection failed")
            
            # Create ExportMailer task
            task = ExportMailerTask()
            
            # Execute task (should not crash)
            success = task.execute()
            
            # PRD Validation: Exception handled gracefully
            self.assertFalse(success, "Task should fail gracefully")
            self.assertIsNotNone(task.error_message, "Error message should be set")
            self.assertIn("Database connection failed", task.error_message)
            
            # PRD Validation: App stays alive (we reach this point)
            self.assertTrue(True, "Application should continue running after exception")
            
            # PRD Validation: Exception logged
            self.assertTrue(os.path.exists("log/hotellabexport.log"), "Log file should exist")

    # PRD §9 Acceptance Criteria 9: INI compatibility
    def test_prd_ac9_ini_compatibility_auto_creation_defaults(self):
        """Test PRD AC9: Missing settings.ini or keys are auto-created with defaults consistent with legacy files."""
        # Remove settings.ini
        if os.path.exists("settings.ini"):
            os.remove("settings.ini")
        
        # Reload configuration (should create new settings.ini)
        reload_config()
        config = get_config()
        
        # PRD Validation: settings.ini was created
        self.assertTrue(os.path.exists("settings.ini"), "settings.ini should be auto-created")
        
        # PRD Validation: Default values match legacy files
        self.assertEqual(config.get("oracleconnectstring"), "v8live/live@v8",
                        "Default oracleconnectstring should match legacy")
        self.assertEqual(config.get("server_port"), "8000",
                        "Default server_port should match legacy")
        self.assertEqual(config.get("schedule_hours"), "8,12,15,18,20",
                        "Default schedule_hours should match legacy")
        
        # Test missing key addition
        config.set("test_new_key", "test_value")
        self.assertEqual(config.get("test_new_key"), "test_value",
                        "Missing keys should be auto-added")
        
        # Test behavior message (from PRD §2.1)
        # This would be tested by checking console output during actual run

    def test_prd_comprehensive_file_encoding(self):
        """Test file encoding compliance with PRD requirements."""
        with patch('features.export_mailer.create_oracle_wrapper') as mock_oracle:
            # Mock Oracle wrapper
            mock_wrapper = Mock()
            mock_oracle.return_value = mock_wrapper
            mock_wrapper.is_connected.return_value = True
            mock_wrapper.connect.return_value = True
            mock_wrapper.execute_scalar.return_value = "TESTHOTEL"
            
            # Mock query results
            test_data = [("тестовое значение",), ("еще значение",)]  # Cyrillic text
            mock_wrapper.stream_query.return_value = [test_data]
            
            # Mock SQL file operations
            with patch('urllib.request.urlretrieve') as mock_download:
                with patch('builtins.open', unittest.mock.mock_open()) as mock_file:
                    mock_file.return_value.read.return_value = "SELECT 1 FROM DUAL"
                    mock_download.return_value = None
                    
                    # Create ExportMailer and run export
                    export_mailer = ExportMailer()
                    success, main_csv, blocks_csv = export_mailer.run_export()
                    
                    # PRD Validation: File encoding from config
                    self.assertTrue(success)
                    
                    # Verify file was opened with correct encoding
                    file_open_calls = mock_file.call_args_list
                    for call in file_open_calls:
                        if 'encoding' in call[1]:
                            self.assertEqual(call[1]['encoding'], 'cp1251',
                                           "Should use file_encoding from config (cp1251 for Cyrillic)")

    def test_prd_comprehensive_date_iteration(self):
        """Test date iteration semantics from PRD §2.2."""
        with patch('features.price_export.create_oracle_wrapper') as mock_oracle:
            with patch('requests.post') as mock_post:
                # Mock Oracle wrapper
                mock_wrapper = Mock()
                mock_oracle.return_value = mock_wrapper
                mock_wrapper.is_connected.return_value = True
                mock_wrapper.connect.return_value = True
                mock_wrapper.execute_scalar.return_value = '{"test": "data"}'
                
                # Mock successful HTTP response
                mock_response = Mock()
                mock_response.status_code = 200
                mock_post.return_value = mock_response
                
                # Create PriceExport with 1 month for testing
                price_export = PriceExport()
                price_export.months = 1
                
                # Mock the export process to capture date iteration
                dates_processed = []
                
                def mock_export_prices_for_date(date_str):
                    dates_processed.append(date_str)
                    return True
                
                price_export.export_prices_for_date = mock_export_prices_for_date
                
                # Run export
                stats = price_export.export_price_range()
                
                # PRD Validation: Date iteration semantics
                # Should iterate day-by-day using relativedelta
                self.assertGreater(len(dates_processed), 0, "Should process dates")
                
                # Verify date format is dd.mm.yyyy
                for date_str in dates_processed:
                    self.assertRegex(date_str, r'\d{2}\.\d{2}\.\d{4}',
                                   "Date format should be dd.mm.yyyy")

    def test_prd_comprehensive_nls_lang_setting(self):
        """Test NLS_LANG environment variable setting from PRD §2.1."""
        with patch('core.oracle.os.environ') as mock_environ:
            # Import and test Oracle module
            from core.oracle import set_nls_lang
            
            # Test NLS_LANG setting
            test_nls_lang = "AMERICAN_AMERICA.CL8MSWIN1251"
            set_nls_lang(test_nls_lang)
            
            # PRD Validation: NLS_LANG set in environment
            mock_environ.__setitem__.assert_called_with("NLS_LANG", test_nls_lang)

    def test_prd_comprehensive_hotel_key_query(self):
        """Test hotel key query from PRD §2.1."""
        with patch('features.export_mailer.create_oracle_wrapper') as mock_oracle:
            # Mock Oracle wrapper
            mock_wrapper = Mock()
            mock_oracle.return_value = mock_wrapper
            mock_wrapper.is_connected.return_value = True
            mock_wrapper.connect.return_value = True
            mock_wrapper.execute_scalar.return_value = "TESTHOTEL"
            
            # Create ExportMailer
            export_mailer = ExportMailer()
            
            # Get hotel key
            hotel_key = export_mailer.get_hotel_key()
            
            # PRD Validation: Exact query from PRD
            expected_query = "select wuss_value from wuss where upper(wuss_name) = upper('UDEF_Hotellabkey')"
            mock_wrapper.execute_scalar.assert_called_with(expected_query)
            
            self.assertEqual(hotel_key, "TESTHOTEL")

    def test_prd_comprehensive_http_response_format(self):
        """Test HTTP response format from PRD §2.3."""
        with patch('features.rate_loader_logic.create_oracle_wrapper') as mock_oracle:
            # Mock Oracle wrapper
            mock_wrapper = Mock()
            mock_oracle.return_value = mock_wrapper
            mock_wrapper.is_connected.return_value = True
            mock_wrapper.connect.return_value = True
            
            # Start HTTP server
            self.assertTrue(start_server())
            time.sleep(0.5)
            
            try:
                import requests
                
                # Test POST /load endpoint
                test_data = {"prices": [{"date": "2023-01-01", "price": 100}]}
                
                response = requests.post(
                    "http://localhost:8000/load",
                    json=test_data,
                    timeout=5
                )
                
                # PRD Validation: Legacy response format
                self.assertEqual(response.status_code, 200)
                self.assertEqual(response.text, "POST request for /load",
                               "Response should match legacy format from PRD §2.3")
                
            except requests.exceptions.ConnectionError:
                self.skipTest("HTTP server not available for testing")

    def test_prd_comprehensive_logging_file_names(self):
        """Test logging file names from PRD §5."""
        # PRD Validation: Unified log file with component prefixes (ADR: Unified Logging System)
        self.assertTrue(os.path.exists("log/rateloader.log"),
                        "Should have unified rateloader.log with component prefixes")
        
        # Check that unified log contains component prefixes
        with open("log/rateloader.log", "r") as f:
            log_content = f.read()
            # Should contain component prefixes from unified logging
            self.assertIn("rateloader", log_content.lower())

    def test_prd_comprehensive_configuration_defaults(self):
        """Test all configuration defaults match PRD §4."""
        config = get_config()
        
        # PRD §4 Validation: All required keys with defaults
        required_defaults = {
            "oracleconnectstring": "v8live/live@v8",
            "nls_lang": "AMERICAN_AMERICA.CL8MSWIN1251",
            "file_encoding": "cp1251",
            "url": "http://fidelio.pro/select.sql",
            "block-url": "http://fidelio.pro/selectBlocks.sql",
            "sendfrom": "radio-moscow@hotellab.io",
            "sendto": "someone@example.com",
            "mailserver": "mail.hotellab.io",
            "port": "25",
            "pwd": "*******",
            "ssl": "0",
            "api_url": "https://app.hotellab.io/privat/api/integration/1c/set-pms-prices/5",
            "token": "1111",
            "months": "12",
            "server_port": "8000",
            "schedule_hours": "8,12,15,18,20"
        }
        
        for key, expected_value in required_defaults.items():
            actual_value = config.get(key)
            self.assertEqual(actual_value, expected_value,
                           f"Default for {key} should be {expected_value}, got {actual_value}")


def run_prd_compliance_validation():
    """Run comprehensive PRD compliance validation."""
    print("RateLoader v3.0 - PRD Compliance Validation")
    print("=" * 60)
    print("Validating all PRD §9 Acceptance Criteria...")
    print()
    
    # Create test suite
    suite = unittest.TestLoader().loadTestsFromTestCase(TestPRDCompliance)
    
    # Run tests
    runner = unittest.TextTestRunner(verbosity=2)
    result = runner.run(suite)
    
    # Print summary
    print("=" * 60)
    print(f"PRD Compliance Tests Run: {result.testsRun}")
    print(f"Failures: {len(result.failures)}")
    print(f"Errors: {len(result.errors)}")
    print(f"Skipped: {len(result.skipped)}")
    
    if result.failures:
        print("\nPRD Compliance Failures:")
        for test, traceback in result.failures:
            print(f"  ❌ {test}")
            print(f"     {traceback.split('AssertionError:')[-1].strip()}")
    
    if result.errors:
        print("\nPRD Compliance Errors:")
        for test, traceback in result.errors:
            print(f"  ❌ {test}")
            print(f"     {traceback.split('Exception:')[-1].strip()}")
    
    success = len(result.failures) == 0 and len(result.errors) == 0
    
    print("\n" + "=" * 60)
    if success:
        print("✅ PRD COMPLIANCE VALIDATION - PASSED")
        print("All acceptance criteria from PRD §9 have been validated.")
        print("RateLoader v3.0 is fully compliant with PRD requirements.")
    else:
        print("❌ PRD COMPLIANCE VALIDATION - FAILED")
        print("Some acceptance criteria are not met. See failures above.")
    print("=" * 60)
    
    return success


if __name__ == "__main__":
    success = run_prd_compliance_validation()
    sys.exit(0 if success else 1)