"""
Property-based tests for directory organization consistency.

This module tests the DirectoryManager's ability to maintain consistent
directory organization across all workflow executions, validating that
the organized file structure is preserved regardless of input variations.
"""

import pytest
import tempfile
import shutil
from pathlib import Path
from typing import List, Tuple
from hypothesis import given, strategies as st, assume, settings
from hypothesis.stateful import RuleBasedStateMachine, rule, initialize, invariant

from tests.workflow_testing.infrastructure.directory_manager import DirectoryManager, StationDirectory
from tests.workflow_testing.infrastructure.test_logger import WorkflowTestLogger


# Hypothesis strategies for generating test data
@st.composite
def valid_station_ids(draw):
    """Generate valid GHCN station identifiers."""
    # GHCN station IDs: 2-letter country + 1-letter network + 8-digit station
    country_codes = ['US', 'CA', 'MX', 'UK', 'FR', 'DE', 'AU', 'JP']
    country = draw(st.sampled_from(country_codes))
    network = draw(st.text(alphabet='ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789', min_size=1, max_size=1))
    station_num = draw(st.text(alphabet='0123456789', min_size=8, max_size=8))
    return country + network + station_num


@st.composite
def workflow_names(draw):
    """Generate valid workflow names."""
    return draw(st.sampled_from(['data', 'analysis', 'simulation', 'validation']))


@st.composite
def directory_paths(draw):
    """Generate valid directory paths for testing."""
    # Generate relative paths that are safe for testing
    components = draw(st.lists(
        st.text(alphabet='abcdefghijklmnopqrstuvwxyz0123456789_-', min_size=1, max_size=10),
        min_size=1, max_size=3
    ))
    return '/'.join(components)


class TestDirectoryOrganization:
    """Test suite for directory organization consistency."""
    
    def setup_method(self):
        """Set up test environment with temporary directory."""
        self.temp_dir = tempfile.mkdtemp(prefix="precipgen_test_")
        self.test_logger = WorkflowTestLogger("directory_organization", log_dir=self.temp_dir)
        
    def teardown_method(self):
        """Clean up test environment."""
        # Close the logger first to release file handles
        if hasattr(self, 'test_logger'):
            self.test_logger.close()
        
        # Clean up temporary directory
        if hasattr(self, 'temp_dir') and Path(self.temp_dir).exists():
            try:
                shutil.rmtree(self.temp_dir)
            except PermissionError:
                # On Windows, sometimes files are still locked, try again after a short delay
                import time
                time.sleep(0.1)
                try:
                    shutil.rmtree(self.temp_dir)
                except PermissionError:
                    # If still failing, just warn and continue
                    print(f"Warning: Could not clean up temporary directory: {self.temp_dir}")
                    pass
    
    @given(station_id=valid_station_ids())
    @settings(max_examples=100, deadline=10000)
    def test_property_directory_organization_consistency(self, station_id: str):
        """
        Property 1: Directory Organization Consistency
        
        For any valid station ID, the system should create or use the organized 
        directory structure ./precipgen_output/{station_id}/ with appropriate 
        subdirectories for data, analysis, simulation, and validation outputs.
        
        **Validates: Requirements 1.1, 1.3**
        """
        # Test with default base directory
        manager = DirectoryManager(base_dir=self.temp_dir)
        
        # Create station directory
        station_dir = manager.create_station_directory(station_id)
        
        # Verify directory structure exists
        assert station_dir.exists(), f"Station directory should exist: {station_dir}"
        assert station_dir.name == station_id, f"Directory name should match station ID: {station_id}"
        
        # Verify required subdirectories exist
        required_subdirs = ['data', 'analysis', 'simulation', 'validation']
        for subdir in required_subdirs:
            subdir_path = station_dir / subdir
            assert subdir_path.exists(), f"Subdirectory should exist: {subdir_path}"
            assert subdir_path.is_dir(), f"Path should be a directory: {subdir_path}"
        
        # Verify directory structure is consistent on repeated calls
        station_dir_2 = manager.create_station_directory(station_id)
        assert station_dir == station_dir_2, "Repeated calls should return same directory"
        
        # Verify all subdirectories still exist after repeated calls
        for subdir in required_subdirs:
            subdir_path = station_dir / subdir
            assert subdir_path.exists(), f"Subdirectory should persist: {subdir_path}"
    
    @given(
        station_id=valid_station_ids(),
        custom_base_dir=directory_paths()
    )
    @settings(max_examples=50, deadline=10000)
    def test_property_custom_output_directory_handling(self, station_id: str, custom_base_dir: str):
        """
        Property 2: Custom Output Directory Handling
        
        For any valid custom output directory path, the system should use that 
        directory instead of the default and maintain the same organizational structure.
        
        **Validates: Requirements 1.2**
        """
        # Create custom base directory within temp directory
        custom_path = Path(self.temp_dir) / custom_base_dir
        
        manager = DirectoryManager()
        
        # Create station directory with custom base
        station_dir = manager.create_station_directory(station_id, base_dir=str(custom_path))
        
        # Verify directory is created in custom location
        expected_path = custom_path / station_id
        assert station_dir == expected_path, f"Directory should be in custom location: {expected_path}"
        assert station_dir.exists(), f"Custom station directory should exist: {station_dir}"
        
        # Verify same organizational structure
        required_subdirs = ['data', 'analysis', 'simulation', 'validation']
        for subdir in required_subdirs:
            subdir_path = station_dir / subdir
            assert subdir_path.exists(), f"Subdirectory should exist in custom location: {subdir_path}"
            assert subdir_path.is_dir(), f"Path should be a directory: {subdir_path}"
    
    @given(
        station_id=valid_station_ids(),
        workflow=workflow_names()
    )
    @settings(max_examples=100, deadline=10000)
    def test_property_workflow_subdirectory_consistency(self, station_id: str, workflow: str):
        """
        Property: Workflow Subdirectory Consistency
        
        For any valid station ID and workflow name, the system should consistently
        create and return the correct workflow subdirectory path.
        
        **Validates: Requirements 1.1, 1.3**
        """
        manager = DirectoryManager(base_dir=self.temp_dir)
        
        # Get workflow subdirectory
        workflow_dir = manager.get_subdirectory(station_id, workflow)
        
        # Verify path structure
        expected_base = Path(self.temp_dir) / station_id / workflow
        assert workflow_dir == expected_base, f"Workflow directory path should be correct: {expected_base}"
        
        # Verify directory exists
        assert workflow_dir.exists(), f"Workflow directory should exist: {workflow_dir}"
        assert workflow_dir.is_dir(), f"Path should be a directory: {workflow_dir}"
        
        # Verify parent station directory has correct structure
        station_dir = workflow_dir.parent
        required_subdirs = ['data', 'analysis', 'simulation', 'validation']
        for subdir in required_subdirs:
            subdir_path = station_dir / subdir
            assert subdir_path.exists(), f"All subdirectories should exist: {subdir_path}"
    
    @given(
        station_ids=st.lists(valid_station_ids(), min_size=1, max_size=10, unique=True)
    )
    @settings(max_examples=20, deadline=15000)
    def test_property_multiple_station_organization(self, station_ids: List[str]):
        """
        Property: Multiple Station Organization
        
        For any list of station IDs, the system should create organized directory
        structures for each station without interference between stations.
        
        **Validates: Requirements 1.1, 1.3**
        """
        # Create a unique temporary directory for this specific test example
        # to ensure isolation between Hypothesis examples
        import tempfile
        with tempfile.TemporaryDirectory(prefix="precipgen_multi_station_test_") as example_temp_dir:
            manager = DirectoryManager(base_dir=example_temp_dir)
            
            created_dirs = []
            
            # Create directories for all stations
            for station_id in station_ids:
                station_dir = manager.create_station_directory(station_id)
                created_dirs.append(station_dir)
                
                # Verify each station has its own directory
                assert station_dir.name == station_id
                assert station_dir.exists()
            
            # Verify all directories exist simultaneously
            for i, station_dir in enumerate(created_dirs):
                assert station_dir.exists(), f"Station directory should persist: {station_ids[i]}"
                
                # Verify subdirectory structure for each station
                required_subdirs = ['data', 'analysis', 'simulation', 'validation']
                for subdir in required_subdirs:
                    subdir_path = station_dir / subdir
                    assert subdir_path.exists(), f"Subdirectory should exist for {station_ids[i]}: {subdir}"
            
            # Verify station directories are independent (no cross-contamination)
            station_list = manager.list_station_directories(base_dir=example_temp_dir)
            assert len(station_list) == len(station_ids), f"Should have correct number of station directories. Expected: {len(station_ids)}, Found: {len(station_list)} ({[s.station_id for s in station_list]})"
            
            found_station_ids = {station.station_id for station in station_list}
            expected_station_ids = set(station_ids)
            assert found_station_ids == expected_station_ids, f"Should find all created stations. Expected: {expected_station_ids}, Found: {found_station_ids}"


class DirectoryOrganizationStateMachine(RuleBasedStateMachine):
    """
    Stateful property-based testing for directory organization.
    
    This tests the DirectoryManager through a series of operations to ensure
    consistency is maintained across complex interaction patterns.
    """
    
    def __init__(self):
        super().__init__()
        self.temp_dir = tempfile.mkdtemp(prefix="precipgen_stateful_test_")
        self.manager = DirectoryManager(base_dir=self.temp_dir)
        self.created_stations = set()
        self.workflow_dirs = {}
    
    @initialize()
    def setup(self):
        """Initialize the state machine."""
        pass
    
    @rule(station_id=valid_station_ids())
    def create_station_directory(self, station_id: str):
        """Rule: Create a station directory."""
        station_dir = self.manager.create_station_directory(station_id)
        self.created_stations.add(station_id)
        
        # Verify directory was created correctly
        assert station_dir.exists()
        assert station_dir.name == station_id
    
    @rule(
        station_id=valid_station_ids(),
        workflow=workflow_names()
    )
    def get_workflow_subdirectory(self, station_id: str, workflow: str):
        """Rule: Get a workflow subdirectory."""
        workflow_dir = self.manager.get_subdirectory(station_id, workflow)
        
        # Track workflow directories
        key = (station_id, workflow)
        if key not in self.workflow_dirs:
            self.workflow_dirs[key] = workflow_dir
        
        # Verify consistency
        assert self.workflow_dirs[key] == workflow_dir
        assert workflow_dir.exists()
        
        # Add station to created set
        self.created_stations.add(station_id)
    
    @rule(station_id=valid_station_ids())
    def list_and_verify_stations(self, station_id: str):
        """Rule: List stations and verify consistency."""
        # Ensure at least one station exists
        if station_id not in self.created_stations:
            self.manager.create_station_directory(station_id)
            self.created_stations.add(station_id)
        
        station_list = self.manager.list_station_directories(base_dir=self.temp_dir)
        found_stations = {station.station_id for station in station_list}
        
        # Verify all created stations are found
        assert self.created_stations.issubset(found_stations)
    
    @invariant()
    def directory_structure_invariant(self):
        """Invariant: Directory structure is always consistent."""
        for station_id in self.created_stations:
            station_path = Path(self.temp_dir) / station_id
            
            # Station directory should exist
            assert station_path.exists(), f"Station directory should exist: {station_id}"
            
            # Required subdirectories should exist
            required_subdirs = ['data', 'analysis', 'simulation', 'validation']
            for subdir in required_subdirs:
                subdir_path = station_path / subdir
                assert subdir_path.exists(), f"Subdirectory should exist: {station_id}/{subdir}"
    
    def teardown(self):
        """Clean up after state machine testing."""
        if hasattr(self, 'temp_dir') and Path(self.temp_dir).exists():
            shutil.rmtree(self.temp_dir)


# Stateful test class
class TestDirectoryOrganizationStateful:
    """Stateful testing for directory organization."""
    
    def test_stateful_directory_organization(self):
        """
        Stateful property test for directory organization consistency.
        
        Tests complex interaction patterns to ensure directory organization
        remains consistent across various operation sequences.
        """
        # Use the correct method for running stateful tests
        from hypothesis.stateful import run_state_machine_as_test
        
        run_state_machine_as_test(
            DirectoryOrganizationStateMachine,
            settings=settings(max_examples=50, stateful_step_count=20, deadline=20000)
        )


# Integration test for the property
def test_directory_organization_property_integration():
    """
    Integration test that validates the directory organization property
    works correctly with the testing framework infrastructure.
    """
    with tempfile.TemporaryDirectory(prefix="precipgen_integration_test_") as temp_dir:
        # Test the property with known values
        manager = DirectoryManager(base_dir=temp_dir)
        
        # Use context manager for proper cleanup
        with WorkflowTestLogger("integration_test", log_dir=temp_dir) as test_logger:
            # Test Salt Lake City station (gold standard)
            station_id = "USC00426128"
            
            test_logger.start_test_execution("Directory organization integration test")
            
            try:
                # Create station directory
                station_dir = manager.create_station_directory(station_id)
                
                # Verify structure
                required_subdirs = ['data', 'analysis', 'simulation', 'validation']
                for subdir in required_subdirs:
                    subdir_path = station_dir / subdir
                    assert subdir_path.exists(), f"Required subdirectory missing: {subdir}"
                
                # Test workflow subdirectories
                for workflow in required_subdirs:
                    workflow_dir = manager.get_subdirectory(station_id, workflow)
                    assert workflow_dir.exists(), f"Workflow directory should exist: {workflow}"
                
                # Test timestamped subdirectories for non-data workflows
                for workflow in ['analysis', 'simulation', 'validation']:
                    timestamped_dir = manager.create_timestamped_subdirectory(station_id, workflow)
                    assert timestamped_dir.exists(), f"Timestamped directory should exist: {workflow}"
                    assert timestamped_dir.parent.name == workflow
                
                test_logger.log_test_result("directory_organization_integration", True, 0.0)
                test_logger.end_test_execution(True, "All directory organization tests passed")
                
            except Exception as e:
                test_logger.log_error_with_context(e, "directory_organization_integration")
                test_logger.end_test_execution(False, f"Integration test failed: {e}")
                raise
