"""
Multi-Config Loader for Pipeline Execution

Main entry point for loading and merging multiple .pipeline.yaml configuration files
for pipeline execution. Integrates discovery, validation, and merging phases.
"""

from pathlib import Path
from typing import Any, Dict

from .config_discovery import ConfigDiscoveryError, discover_and_validate_configs
from .config_validation import ConfigValidationError, validate_all_pipeline_configs
from .job_merging import (
    JobMergingError,
    merge_and_validate_pipeline_jobs,
    remove_source_metadata_from_jobs,
)


class MultiConfigError(Exception):
    """Base exception for multi-config loading errors."""

    pass


def load_multi_pipeline_configs(
    org_dir: Path, stage_name: str, verbose: bool = False, run_dir: Path = None
) -> Dict[str, Any]:
    """
    Load and merge multiple pipeline configs for a stage.

    This is the main entry point that orchestrates the complete multi-config workflow:
    1. Discovery: Find all .pipeline.yaml files recursively
    2. Validation: Validate all configs upfront before processing
    3. Merging: Combine jobs from all configs into single execution plan
    4. Format: Return in format compatible with existing pipeline

    Args:
        org_dir: Organization directory (e.g., orgs/demo_test/)
        stage_name: Target stage name (e.g., "generate", "k8s")
        verbose: Whether to print detailed progress information
        run_dir: Run directory to write job metadata mapping file (optional)

    Returns:
        Merged configuration dictionary compatible with existing pipeline format

    Raises:
        MultiConfigError: If any phase of the multi-config loading fails
    """
    if verbose:
        print(f"🔍 Starting multi-config discovery for stage '{stage_name}'...")

    try:
        # Phase 1: Discovery
        if verbose:
            print(
                f"📋 Phase 1: Discovering .pipeline.yaml files in {org_dir}/billing/pipeline-configs/"
            )

        config_files = discover_and_validate_configs(org_dir, verbose=verbose)

        if verbose:
            print(f"✅ Discovered {len(config_files)} config file(s)")

        # Phase 2: Validation
        if verbose:
            print(f"\n🔍 Phase 2: Validating all configs for stage '{stage_name}'...")

        validation_result = validate_all_pipeline_configs(config_files, stage_name)

        if verbose:
            print(f"✅ All {len(config_files)} config(s) validated successfully")

            if validation_result.warnings:
                print("\n⚠️  Validation warnings:")
                for warning in validation_result.warnings:
                    print(f"   • {warning}")

        # Phase 3: Merging
        if verbose:
            print("\n🔄 Phase 3: Merging jobs from all configs...")

        merged_config = merge_and_validate_pipeline_jobs(
            validation_result.configs, stage_name, verbose=verbose
        )

        if verbose:
            print(
                f"✅ Successfully merged {merged_config.total_jobs} job(s) from {len(merged_config.source_configs)} config(s)"
            )

        # Phase 4: Format for pipeline
        if verbose:
            print("\n📦 Phase 4: Formatting for pipeline execution...")

        # Build job metadata mapping before cleaning jobs
        from .job_merging import build_job_metadata_mapping

        job_metadata_mapping = build_job_metadata_mapping(merged_config.jobs)

        # Write job metadata mapping to run directory if provided
        if run_dir and job_metadata_mapping:
            import json

            mapping_file = run_dir / "job_metadata_mapping.json"
            try:
                with open(mapping_file, "w") as f:
                    json.dump(job_metadata_mapping, f, indent=2)
                if verbose:
                    print(f"📝 Job metadata mapping written to: {mapping_file}")
                    print(f"   • {len(job_metadata_mapping)} job mappings saved")
            except Exception as e:
                if verbose:
                    print(f"⚠️  Warning: Could not write job metadata mapping: {e}")

        # Remove source tracking metadata from jobs before execution
        clean_jobs = remove_source_metadata_from_jobs(merged_config.jobs)

        # Return in existing pipeline format
        pipeline_config = {
            "stages": {stage_name: clean_jobs},
            "_metadata": {
                "discovery_method": "multi_config",
                "source_configs": merged_config.source_configs,
                "total_jobs": merged_config.total_jobs,
                "config_files": [str(cf.relative_path) for cf in config_files],
            },
            "_job_metadata_mapping": job_metadata_mapping,
        }

        if verbose:
            print("✅ Multi-config loading complete!")
            print(
                f"   • {pipeline_config['_metadata']['total_jobs']} jobs ready for execution"
            )
            print(
                f"   • Source configs: {', '.join(pipeline_config['_metadata']['source_configs'])}"
            )

        return pipeline_config

    except ConfigDiscoveryError as e:
        raise MultiConfigError(f"Config discovery failed: {e}")
    except ConfigValidationError as e:
        error_summary = e.get_error_summary()
        raise MultiConfigError(f"Config validation failed:\n{error_summary}")
    except JobMergingError as e:
        raise MultiConfigError(f"Job merging failed: {e}")
    except Exception as e:
        raise MultiConfigError(f"Unexpected error during multi-config loading: {e}")


def load_single_stage_config(
    org_dir: Path, stage_name: str, verbose: bool = False, run_dir: Path = None
) -> Any:
    """
    Load configuration for a single stage from multi-config system.

    This function extracts just the configuration for the specified stage,
    which is what most pipeline stages expect.

    Args:
        org_dir: Organization directory
        stage_name: Target stage name
        verbose: Whether to print progress information
        run_dir: Run directory to write job metadata mapping file (optional)

    Returns:
        Stage configuration (typically a list of jobs)

    Raises:
        MultiConfigError: If loading fails
    """
    full_config = load_multi_pipeline_configs(
        org_dir, stage_name, verbose=verbose, run_dir=run_dir
    )

    # Extract just the stage configuration
    stage_config = full_config.get("stages", {}).get(stage_name, [])

    if verbose:
        job_count = len(stage_config) if isinstance(stage_config, list) else 1
        print(f"📋 Returning {job_count} job(s) for stage '{stage_name}'")

    return stage_config


def get_multi_config_metadata(org_dir: Path, stage_name: str) -> Dict[str, Any]:
    """
    Get metadata about multi-config setup without loading full configs.

    Args:
        org_dir: Organization directory
        stage_name: Target stage name

    Returns:
        Dictionary with metadata about discovered configs

    Raises:
        MultiConfigError: If discovery fails
    """
    try:
        config_files = discover_and_validate_configs(org_dir, verbose=False)

        # Count jobs per config for this stage
        jobs_per_config = {}
        total_jobs = 0

        for config_file in config_files:
            content = config_file.load_content()
            stages = content.get("stages", {})

            if stage_name in stages:
                stage_config = stages[stage_name]
                if isinstance(stage_config, list):
                    job_count = len(stage_config)
                elif isinstance(stage_config, dict):
                    job_count = len(stage_config.get("jobs", []))
                else:
                    job_count = 0

                jobs_per_config[str(config_file.relative_path)] = job_count
                total_jobs += job_count

        return {
            "total_configs": len(config_files),
            "configs_with_stage": len(jobs_per_config),
            "total_jobs": total_jobs,
            "jobs_per_config": jobs_per_config,
            "config_files": [str(cf.relative_path) for cf in config_files],
            "stage": stage_name,
        }

    except ConfigDiscoveryError as e:
        raise MultiConfigError(f"Failed to get multi-config metadata: {e}")


def validate_multi_config_setup(org_dir: Path, stage_name: str) -> Dict[str, Any]:
    """
    Validate multi-config setup without loading full configs.

    Useful for pre-flight checks and CLI validation commands.

    Args:
        org_dir: Organization directory
        stage_name: Target stage name

    Returns:
        Dictionary with validation results
    """
    try:
        # Discovery phase
        config_files = discover_and_validate_configs(org_dir, verbose=False)

        # Validation phase
        validation_result = validate_all_pipeline_configs(config_files, stage_name)

        return {
            "valid": True,
            "total_configs": len(config_files),
            "errors": [],
            "warnings": validation_result.warnings or [],
            "config_files": [str(cf.relative_path) for cf in config_files],
        }

    except ConfigDiscoveryError as e:
        return {
            "valid": False,
            "total_configs": 0,
            "errors": [f"Discovery error: {e}"],
            "warnings": [],
            "config_files": [],
        }
    except ConfigValidationError as e:
        return {
            "valid": False,
            "total_configs": len(e.errors) if hasattr(e, "errors") else 0,
            "errors": [str(error) for error in e.errors]
            if hasattr(e, "errors")
            else [str(e)],
            "warnings": [],
            "config_files": [],
        }
    except Exception as e:
        return {
            "valid": False,
            "total_configs": 0,
            "errors": [f"Unexpected error: {e}"],
            "warnings": [],
            "config_files": [],
        }
