"""
Configuration models for the generate package.

Defines data models for folder-based configuration loading and validation.
"""

from dataclasses import dataclass, field
from datetime import datetime
from pathlib import Path
from typing import List, Dict, Any, Optional
import yaml
import os
import re
import uuid


@dataclass
class UsageType:
    """Individual usage type specification."""
    name: str


@dataclass
class Operation:
    """Operation specification with usage types."""
    name: str
    usage_types: List[str]


@dataclass
class ResourceType:
    """Resource type specification with operations."""
    name: str
    operations: List[Operation]


@dataclass
class UsageFamily:
    """Usage family specification with resource types."""
    name: str
    resource_types: List[ResourceType]


@dataclass
class Service:
    """Service specification with usage families."""
    name: str
    usage_families: List[UsageFamily]


@dataclass
class Filter:
    """Filter specification for resource matching."""
    cloud_provider: str
    variance_profile: str
    anomaly_profile: Optional[str]
    services: List[Service]


@dataclass
class GenerationJob:
    """Individual generation job specification."""
    name: str
    start_date: datetime
    end_date: datetime
    total_cost: float
    resource_count: int
    filters: List[Filter]
    uuid: str = field(default_factory=lambda: str(uuid.uuid4()))
    
    def __post_init__(self):
        """Validate generation job parameters."""
        # Validate date range
        if self.start_date >= self.end_date:
            raise ValueError(f"End date must be after start date in job '{self.name}'")
        
        # Validate costs
        if self.total_cost <= 0:
            raise ValueError(f"Total cost must be positive in job '{self.name}'")
        
        if self.resource_count <= 0:
            raise ValueError(f"Resource count must be positive in job '{self.name}'")
        
        # Validate filters
        if not self.filters:
            raise ValueError(f"At least one filter required in job '{self.name}'")


@dataclass
class GenerateConfig:
    """Complete generation configuration."""
    generation_jobs: List[GenerationJob]
    source_file: Optional[Path] = None
    
    def __post_init__(self):
        """Validate generation configuration."""
        if not self.generation_jobs:
            raise ValueError("At least one generation job is required")
        
        # Validate job names are unique
        job_names = [job.name for job in self.generation_jobs]
        if len(job_names) != len(set(job_names)):
            duplicates = [name for name in job_names if job_names.count(name) > 1]
            raise ValueError(f"Duplicate job names found: {duplicates}")


def load_generation_config(config_path: Path) -> GenerateConfig:
    """
    Load generation configuration from a single YAML file.
    
    Args:
        config_path: Path to YAML configuration file
        
    Returns:
        GenerateConfig object
        
    Raises:
        FileNotFoundError: If config file doesn't exist
        ValueError: If config is invalid
    """
    if not config_path.exists():
        raise FileNotFoundError(f"Configuration file not found: {config_path}")
    
    with open(config_path, 'r') as f:
        config_data = yaml.safe_load(f)
    
    # Resolve environment variables
    config_data = _resolve_environment_variables(config_data)
    
    # Validate top-level structure
    if not isinstance(config_data, dict):
        raise ValueError(f"Config file must contain a dictionary: {config_path}")
    
    if 'generation_jobs' not in config_data:
        raise ValueError(f"Config file must contain 'generation_jobs' section: {config_path}")
    
    # Parse generation jobs
    jobs = []
    for job_data in config_data['generation_jobs']:
        job = _parse_generation_job(job_data)
        jobs.append(job)
    
    return GenerateConfig(
        generation_jobs=jobs,
        source_file=config_path
    )


def load_generation_configs(config_path: Path) -> List[GenerateConfig]:
    """
    Load generation configurations from file or folder recursively.
    
    Args:
        config_path: Path to file or folder containing configurations
        
    Returns:
        List of GenerateConfig objects
        
    Raises:
        FileNotFoundError: If config path doesn't exist
        ValueError: If any config is invalid
    """
    if not config_path.exists():
        raise FileNotFoundError(f"Configuration path not found: {config_path}")
    
    configs = []
    
    if config_path.is_file():
        # Single file
        if config_path.suffix.lower() in ['.yaml', '.yml']:
            configs.append(load_generation_config(config_path))
        else:
            raise ValueError(f"Config file must have .yaml or .yml extension: {config_path}")
    
    elif config_path.is_dir():
        # Directory - search recursively
        yaml_files = []
        for pattern in ['*.yaml', '*.yml']:
            yaml_files.extend(config_path.rglob(pattern))
        
        if not yaml_files:
            raise ValueError(f"No YAML files found in directory: {config_path}")
        
        for yaml_file in sorted(yaml_files):
            configs.append(load_generation_config(yaml_file))
    
    else:
        raise ValueError(f"Config path must be a file or directory: {config_path}")
    
    return configs


def merge_generation_configs(configs: List[GenerateConfig]) -> GenerateConfig:
    """
    Merge multiple generation configurations into a single configuration.
    
    Args:
        configs: List of GenerateConfig objects to merge
        
    Returns:
        Single merged GenerateConfig
        
    Raises:
        ValueError: If merge conflicts exist
    """
    if not configs:
        raise ValueError("At least one config required for merging")
    
    if len(configs) == 1:
        return configs[0]
    
    # Collect all jobs
    all_jobs = []
    job_names = set()
    
    for config in configs:
        for job in config.generation_jobs:
            # Ensure unique job names across configs
            original_name = job.name
            counter = 1
            while job.name in job_names:
                job.name = f"{original_name}_{counter}"
                counter += 1
            
            job_names.add(job.name)
            all_jobs.append(job)
    
    return GenerateConfig(
        generation_jobs=all_jobs,
        source_file=None  # Merged config has no single source
    )


def _resolve_environment_variables(data: Any) -> Any:
    """
    Recursively resolve environment variables in configuration data.
    
    Args:
        data: Configuration data (dict, list, or str)
        
    Returns:
        Data with environment variables resolved
    """
    if isinstance(data, dict):
        return {key: _resolve_environment_variables(value) for key, value in data.items()}
    elif isinstance(data, list):
        return [_resolve_environment_variables(item) for item in data]
    elif isinstance(data, str):
        # Replace ${VAR} patterns with environment variables
        def replace_env_var(match):
            var_name = match.group(1)
            return os.getenv(var_name, match.group(0))  # Return original if not found
        
        return re.sub(r'\$\{([^}]+)\}', replace_env_var, data)
    else:
        return data


def _parse_generation_job(job_data: Dict[str, Any]) -> GenerationJob:
    """
    Parse generation job from dictionary data.
    
    Args:
        job_data: Job data dictionary
        
    Returns:
        GenerationJob object
    """
    # Required fields
    required_fields = ['name', 'start_date', 'end_date', 'total_cost', 'resource_count', 'filters']
    for field in required_fields:
        if field not in job_data:
            raise ValueError(f"Missing required field '{field}' in generation job")
    
    # Parse filters
    filters = []
    for filter_data in job_data['filters']:
        filter_obj = _parse_filter(filter_data)
        filters.append(filter_obj)
    
    return GenerationJob(
        name=job_data['name'],
        start_date=datetime.fromisoformat(job_data['start_date']),
        end_date=datetime.fromisoformat(job_data['end_date']),
        total_cost=float(job_data['total_cost']),
        resource_count=int(job_data['resource_count']),
        filters=filters
    )


def _parse_filter(filter_data: Dict[str, Any]) -> Filter:
    """
    Parse filter from dictionary data.
    
    Args:
        filter_data: Filter data dictionary
        
    Returns:
        Filter object
    """
    # Required fields
    required_fields = ['cloud_provider', 'variance_profile', 'services']
    for field in required_fields:
        if field not in filter_data:
            raise ValueError(f"Missing required field '{field}' in filter")
    
    # Parse services
    services = []
    for service_data in filter_data['services']:
        service_obj = _parse_service(service_data)
        services.append(service_obj)
    
    return Filter(
        cloud_provider=filter_data['cloud_provider'],
        variance_profile=filter_data['variance_profile'],
        anomaly_profile=filter_data.get('anomaly_profile'),
        services=services
    )


def _parse_service(service_data: Dict[str, Any]) -> Service:
    """
    Parse service from dictionary data.
    
    Args:
        service_data: Service data dictionary
        
    Returns:
        Service object
    """
    if 'name' not in service_data:
        raise ValueError("Missing required field 'name' in service")
    
    if 'usage_families' not in service_data:
        raise ValueError("Missing required field 'usage_families' in service")
    
    # Parse usage families
    usage_families = []
    for family_data in service_data['usage_families']:
        family_obj = _parse_usage_family(family_data)
        usage_families.append(family_obj)
    
    return Service(
        name=service_data['name'],
        usage_families=usage_families
    )


def _parse_usage_family(family_data: Dict[str, Any]) -> UsageFamily:
    """
    Parse usage family from dictionary data.
    
    Args:
        family_data: Usage family data dictionary
        
    Returns:
        UsageFamily object
    """
    if 'name' not in family_data:
        raise ValueError("Missing required field 'name' in usage family")
    
    if 'resource_types' not in family_data:
        raise ValueError("Missing required field 'resource_types' in usage family")
    
    # Parse resource types
    resource_types = []
    for resource_data in family_data['resource_types']:
        resource_obj = _parse_resource_type(resource_data)
        resource_types.append(resource_obj)
    
    return UsageFamily(
        name=family_data['name'],
        resource_types=resource_types
    )


def _parse_resource_type(resource_data: Dict[str, Any]) -> ResourceType:
    """
    Parse resource type from dictionary data.
    
    Args:
        resource_data: Resource type data dictionary
        
    Returns:
        ResourceType object
    """
    if 'name' not in resource_data:
        raise ValueError("Missing required field 'name' in resource type")
    
    if 'operations' not in resource_data:
        raise ValueError("Missing required field 'operations' in resource type")
    
    # Parse operations
    operations = []
    for operation_data in resource_data['operations']:
        operation_obj = _parse_operation(operation_data)
        operations.append(operation_obj)
    
    return ResourceType(
        name=resource_data['name'],
        operations=operations
    )


def _parse_operation(operation_data: Dict[str, Any]) -> Operation:
    """
    Parse operation from dictionary data.
    
    Args:
        operation_data: Operation data dictionary
        
    Returns:
        Operation object
    """
    if 'name' not in operation_data:
        raise ValueError("Missing required field 'name' in operation")
    
    if 'usage_types' not in operation_data:
        raise ValueError("Missing required field 'usage_types' in operation")
    
    # Usage types should be a list of strings
    usage_types = operation_data['usage_types']
    if not isinstance(usage_types, list):
        raise ValueError("Field 'usage_types' must be a list")
    
    return Operation(
        name=operation_data['name'],
        usage_types=usage_types
    )


def validate_generation_config(config: GenerateConfig) -> List[str]:
    """
    Validate generation configuration and return list of validation errors.
    
    Args:
        config: GenerateConfig to validate
        
    Returns:
        List of validation error messages (empty if valid)
    """
    errors = []
    
    # Validate each job
    for job in config.generation_jobs:
        # Validate date range (dates are already datetime objects)
        if job.start_date >= job.end_date:
            errors.append(f"Job '{job.name}': end_date must be after start_date")
        
        # Validate costs and counts
        if job.total_cost <= 0:
            errors.append(f"Job '{job.name}': total_cost must be positive")
        
        if job.resource_count <= 0:
            errors.append(f"Job '{job.name}': resource_count must be positive")
        
        # Validate filters
        if not job.filters:
            errors.append(f"Job '{job.name}': at least one filter required")
        
        for i, filter_obj in enumerate(job.filters):
            if not filter_obj.services:
                errors.append(f"Job '{job.name}' filter {i}: at least one service required")
    
    return errors