"""
ConfigService - Handle YAML configuration files for pipeline and upload.

Primary Responsibility: Load, validate, and parse YAML configuration files

Capabilities:
- Pipeline configuration loading (main.yaml)
- Upload configuration loading (config.yaml)
- Schema validation and parsing
- Configuration format standardization
"""

from pathlib import Path
from typing import Any, Dict, List

import yaml


class ConfigurationError(Exception):
    """Raised when configuration loading or validation fails."""

    pass


class ConfigService:
    """
    Service for handling YAML configuration files.

    Provides centralized configuration loading and validation for:
    - Pipeline configurations (billing generation)
    - Upload configurations (CloudZero API upload)

    Features:
    - YAML parsing with error handling
    - Configuration schema validation
    - Format standardization
    - Clear error reporting
    """

    def __init__(self):
        """Initialize ConfigService."""
        pass

    def load_pipeline_config(self, config_path: Path) -> Dict[str, Any]:
        """
        Load and validate pipeline configuration from YAML file.

        Pipeline configurations define the stages and parameters for
        generating synthetic billing data.

        Args:
            config_path: Path to pipeline configuration YAML file

        Returns:
            Parsed and validated pipeline configuration

        Raises:
            ConfigurationError: If file cannot be loaded or is invalid
            FileNotFoundError: If configuration file doesn't exist
        """
        if not config_path.exists():
            raise FileNotFoundError(
                f"Pipeline configuration file not found: {config_path}\n"
                f"Current working directory: {Path.cwd()}\n"
                f"Absolute path attempted: {config_path.resolve()}"
            )

        with open(config_path) as f:
            config = yaml.safe_load(f)

        if config is None:
            raise ConfigurationError(
                f"Pipeline configuration file is empty: {config_path}"
            )

        # Validate pipeline configuration structure
        self.validate_pipeline_config_schema(config, config_path)

        return config

    def load_upload_config(self, config_path: Path) -> Dict[str, Any]:
        """
        Load and validate upload configuration from YAML file.

        Upload configurations define connection details and settings
        for uploading generated data to CloudZero API.

        Args:
            config_path: Path to upload configuration YAML file

        Returns:
            Parsed and validated upload configuration

        Raises:
            ConfigurationError: If file cannot be loaded or is invalid
            FileNotFoundError: If configuration file doesn't exist
        """
        if not config_path.exists():
            raise FileNotFoundError(
                f"Upload configuration file not found: {config_path}\n"
                f"Current working directory: {Path.cwd()}\n"
                f"Absolute path attempted: {config_path.resolve()}"
            )

        with open(config_path) as f:
            config = yaml.safe_load(f)

        if config is None:
            raise ConfigurationError(
                f"Upload configuration file is empty: {config_path}"
            )

        # Validate upload configuration structure
        self.validate_upload_config_schema(config, config_path)

        return config

    def validate_pipeline_config_schema(
        self, config: Dict[str, Any], config_path: Path
    ) -> None:
        """
        Validate pipeline configuration against expected schema.

        Args:
            config: Parsed configuration dictionary
            config_path: Path to config file (for error reporting)

        Raises:
            ConfigurationError: If configuration schema is invalid
        """
        errors = []

        # Check for required top-level structure
        if not isinstance(config, dict):
            errors.append("Configuration must be a dictionary")

        if "stages" not in config:
            errors.append("Configuration must contain 'stages' section")
        else:
            stages = config["stages"]
            if not isinstance(stages, dict):
                errors.append("'stages' must be a dictionary")
            # Validate generate stage if present
            elif "generate" in stages:
                self._validate_generate_stage(stages["generate"], errors)

        if errors:
            raise ConfigurationError(
                f"Invalid pipeline configuration in {config_path}:\n"
                + "\n".join(f"  - {error}" for error in errors)
            )

    def validate_upload_config_schema(
        self, config: Dict[str, Any], config_path: Path
    ) -> None:
        """
        Validate upload configuration against expected schema.

        Args:
            config: Parsed configuration dictionary
            config_path: Path to config file (for error reporting)

        Raises:
            ConfigurationError: If configuration schema is invalid
        """
        errors = []

        # Check for required top-level structure
        if not isinstance(config, dict):
            errors.append("Configuration must be a dictionary")

        # Validate organization details
        if "org_name" not in config:
            errors.append("Configuration must contain 'org_name'")

        # Validate connections section if present
        if "connections" in config:
            connections = config["connections"]
            if not isinstance(connections, dict):
                errors.append("'connections' must be a dictionary")

        # Validate upload section if present
        if "upload" in config:
            upload = config["upload"]
            if not isinstance(upload, dict):
                errors.append("'upload' must be a dictionary")

        if errors:
            raise ConfigurationError(
                f"Invalid upload configuration in {config_path}:\n"
                + "\n".join(f"  - {error}" for error in errors)
            )

    def _validate_generate_stage(self, generate_config: Any, errors: List[str]) -> None:
        """
        Validate generate stage configuration.

        Args:
            generate_config: Generate stage configuration
            errors: List to append validation errors to
        """
        if not isinstance(generate_config, list):
            errors.append("'generate' stage must be a list of jobs")
            return

        if len(generate_config) == 0:
            errors.append("'generate' stage cannot be empty")
            return

        for job_index, job in enumerate(generate_config):
            if not isinstance(job, dict):
                errors.append(f"Generate job {job_index} must be a dictionary")
                continue

            # Check required job fields
            required_fields = [
                "reference_data",
                "start_date",
                "end_date",
                "total_cost",
                "resource_count",
            ]
            for field in required_fields:
                if field not in job:
                    errors.append(
                        f"Generate job {job_index} missing required field '{field}'"
                    )

            # Validate filters section
            if "filters" in job:
                filters = job["filters"]
                if not isinstance(filters, list):
                    errors.append(f"Generate job {job_index} 'filters' must be a list")
                elif len(filters) == 0:
                    errors.append(f"Generate job {job_index} 'filters' cannot be empty")
                else:
                    self._validate_filters(filters, job_index, errors)

    def _validate_filters(
        self, filters: List[Any], job_index: int, errors: List[str]
    ) -> None:
        """
        Validate filters configuration.

        Args:
            filters: List of filter configurations
            job_index: Index of job for error reporting
            errors: List to append validation errors to
        """
        for filter_index, filter_config in enumerate(filters):
            if not isinstance(filter_config, dict):
                errors.append(
                    f"Job {job_index} filter {filter_index} must be a dictionary"
                )
                continue

            # Check required filter fields
            if "cloud_provider" not in filter_config:
                errors.append(
                    f"Job {job_index} filter {filter_index} missing 'cloud_provider'"
                )

            # Validate services section
            if "services" in filter_config:
                services = filter_config["services"]
                if not isinstance(services, list):
                    errors.append(
                        f"Job {job_index} filter {filter_index} 'services' must be a list"
                    )

    def get_config_summary(
        self, config: Dict[str, Any], config_type: str
    ) -> Dict[str, Any]:
        """
        Get summary information about a configuration.

        Args:
            config: Parsed configuration dictionary
            config_type: "pipeline" or "upload"

        Returns:
            Dictionary with configuration summary
        """
        if config_type == "pipeline":
            return self._get_pipeline_config_summary(config)
        elif config_type == "upload":
            return self._get_upload_config_summary(config)
        else:
            raise ValueError(f"Unknown config type: {config_type}")

    def _get_pipeline_config_summary(self, config: Dict[str, Any]) -> Dict[str, Any]:
        """Get summary of pipeline configuration."""
        summary = {
            "total_stages": len(config.get("stages", {})),
            "has_generate_stage": "generate" in config.get("stages", {}),
            "generate_jobs": 0,
            "providers": set(),
            "services": set(),
        }

        if summary["has_generate_stage"]:
            generate_config = config["stages"]["generate"]
            summary["generate_jobs"] = len(generate_config)

            # Collect providers and services
            for job in generate_config:
                for filter_config in job.get("filters", []):
                    provider = filter_config.get("cloud_provider")
                    if provider:
                        summary["providers"].add(provider)

                    for service in filter_config.get("services", []):
                        service_name = service.get("name")
                        if service_name:
                            summary["services"].add(service_name)

        # Convert sets to lists for JSON serialization
        summary["providers"] = sorted(summary["providers"])
        summary["services"] = sorted(summary["services"])

        return summary

    def _get_upload_config_summary(self, config: Dict[str, Any]) -> Dict[str, Any]:
        """Get summary of upload configuration."""
        summary = {
            "org_name": config.get("org_name", ""),
            "has_connections": "connections" in config,
            "has_upload_config": "upload" in config,
            "providers": [],
        }

        if summary["has_connections"]:
            connections = config["connections"]
            summary["providers"] = sorted(connections.keys())

        return summary


def create_config_service() -> ConfigService:
    """
    Factory function to create ConfigService.

    Returns:
        Configured ConfigService instance
    """
    return ConfigService()
