"""
Analysis Phase Orchestrator

Coordinates all static analyzers to produce quality metrics for the ANALYSIS phase.
This is the first phase of the 5-phase pipeline.
"""

from __future__ import annotations

import asyncio
import time
from pathlib import Path
from typing import Any

import structlog

from warden.analysis.application.metrics_aggregator import MetricsAggregator
from warden.analysis.domain.quality_metrics import (
    QualityMetrics,
)
from warden.ast.domain.enums import CodeLanguage
from warden.cleaning.application.analyzers.complexity_analyzer import ComplexityAnalyzer
from warden.cleaning.application.analyzers.documentation_analyzer import DocumentationAnalyzer
from warden.cleaning.application.analyzers.duplication_analyzer import DuplicationAnalyzer
from warden.cleaning.application.analyzers.lsp_diagnostics_analyzer import LSPDiagnosticsAnalyzer
from warden.cleaning.application.analyzers.magic_number_analyzer import MagicNumberAnalyzer
from warden.cleaning.application.analyzers.maintainability_analyzer import MaintainabilityAnalyzer
from warden.cleaning.application.analyzers.naming_analyzer import NamingAnalyzer
from warden.cleaning.application.analyzers.testability_analyzer import TestabilityAnalyzer
from warden.shared.infrastructure.ignore_matcher import IgnoreMatcher
from warden.shared.utils.language_utils import get_language_from_path
from warden.validation.domain.frame import CodeFile

logger = structlog.get_logger()


class AnalysisPhase:
    """
    Analysis Phase orchestrator for quality metrics calculation.

    Coordinates multiple analyzers to produce a comprehensive quality score
    for the Panel UI's Summary tab.
    """

    def __init__(
        self,
        config: dict[str, Any] | None = None,
        progress_callback: callable | None = None,
        project_root: Path | None = None,
        use_gitignore: bool = True,
    ) -> None:
        """
        Initialize Analysis Phase.

        Args:
            config: Analysis configuration including weights and LLM settings
            progress_callback: Optional callback for progress updates
        """
        self.config = config or self._get_default_config()
        self.progress_callback = progress_callback

        # Initialize analyzers
        self.analyzers = {
            "complexity": ComplexityAnalyzer(),
            "duplication": DuplicationAnalyzer(),
            "naming": NamingAnalyzer(),
            "magic_numbers": MagicNumberAnalyzer(),
            "maintainability": MaintainabilityAnalyzer(),
            "documentation": DocumentationAnalyzer(),
            "testability": TestabilityAnalyzer(),
            "lsp_diagnostics": LSPDiagnosticsAnalyzer(),
        }

        # Get analysis level from config
        from warden.pipeline.domain.enums import AnalysisLevel

        level_str = self.config.get("analysis_level", "standard")
        try:
            self.analysis_level = AnalysisLevel(level_str.lower())
        except (ValueError, AttributeError):
            self.analysis_level = AnalysisLevel.STANDARD

        # Initialize IgnoreMatcher
        self.project_root = Path(project_root) if project_root else Path.cwd()
        self.ignore_matcher = IgnoreMatcher(self.project_root, use_gitignore=use_gitignore)

        # Get metric weights from config
        self.weights = self.config.get("weights", self._get_default_weights())

        # Initialize Metrics Aggregator
        self.metrics_aggregator = MetricsAggregator(self.weights)

        # Caching
        self._cache_file = self.project_root / ".warden" / "cache" / "analysis_metrics.json"
        self._cache_data = self._load_cache()

        logger.info(
            "analysis_phase_initialized",
            analyzer_count=len(self.analyzers),
            weights=self.weights,
            cached_entries=len(self._cache_data),
        )

    def _get_default_config(self) -> dict[str, Any]:
        """Get default analysis configuration."""
        return {
            "enabled": True,
            "use_llm": False,  # LLM enhancement disabled by default
            "llm_provider": "azure_openai",
            "weights": self._get_default_weights(),
            "timeout": 5.0,  # 5 second timeout for analysis phase
        }

    def _get_default_weights(self) -> dict[str, float]:
        """Get default metric weights for score calculation."""
        return {
            "complexity": 0.25,
            "duplication": 0.20,
            "maintainability": 0.20,
            "naming": 0.15,
            "documentation": 0.10,
            "testability": 0.10,
        }

    def _load_cache(self) -> dict[str, Any]:
        """Load analysis cache."""
        try:
            if self._cache_file.exists():
                import json

                return json.loads(self._cache_file.read_text())
        except Exception as e:
            logger.warning("analysis_cache_load_failed", error=str(e))
        return {}

    def _save_cache(self):
        """Save analysis cache."""
        try:
            import json

            self._cache_file.parent.mkdir(parents=True, exist_ok=True)
            self._cache_file.write_text(json.dumps(self._cache_data, indent=2))
        except Exception as e:
            logger.warning("analysis_cache_save_failed", error=str(e))

    def _get_cached_result(self, code_file: CodeFile) -> dict[str, Any] | None:
        """Retrieve cached result if valid."""
        if not code_file.hash:
            return None

        cached = self._cache_data.get(code_file.path)
        if cached and cached.get("hash") == code_file.hash:
            # Validate schema version if needed, for now trust it
            return cached.get("result")
        return None

    def _update_cache(self, code_file: CodeFile, result: dict[str, Any]):
        """Update cache with new result."""
        if not code_file.hash:
            return

        self._cache_data[code_file.path] = {"hash": code_file.hash, "timestamp": time.time(), "result": result}

    async def execute_async(
        self,
        code_files: list[CodeFile],
        pipeline_context: Any | None = None,
        **kwargs: Any,
    ) -> QualityMetrics:
        """
        Execute analysis phase.

        Args:
            code_files: List of code files to analyze
            pipeline_context: Pipeline context

        Returns:
            QualityMetrics object
        """
        # Filter handled files
        code_files = [f for f in code_files if not self.ignore_matcher.should_ignore_file(Path(f.path))]

        if not code_files:
            return QualityMetrics()

        start_time = time.perf_counter()

        # Check cache
        files_to_analyze = []
        all_results = {}
        cached_count = 0

        for cf in code_files:
            cached_res = self._get_cached_result(cf)
            if cached_res:
                all_results[cf.path] = cached_res
                cached_count += 1
            else:
                files_to_analyze.append(cf)

        logger.info(
            "analysis_phase_started",
            total_files=len(code_files),
            cached_files=cached_count,
            files_to_analyze=len(files_to_analyze),
        )

        # Notify progress callback
        if self.progress_callback:
            self.progress_callback(
                "progress_init",
                {
                    "total_units": len(files_to_analyze),  # Only report work to be done
                    "phase": "ANALYSIS",
                },
            )

        try:
            if files_to_analyze:
                # Limit concurrency to prevent system overload (340+ files * 7 analyzers = 2000+ tasks)
                # Use CPU-bound friendly limit: min(CPUs, 8)
                import os

                concurrency_limit = min(os.cpu_count() or 4, 8)
                semaphore = asyncio.Semaphore(concurrency_limit)

                logger.info("analysis_concurrency_set", limit=concurrency_limit)

                async def analyze_with_limit(cf):
                    async with semaphore:
                        result = await self._analyze_file_async(cf, pipeline_context)
                        if self.progress_callback:
                            self.progress_callback(
                                "progress_update", {"increment": 1, "phase": "ANALYSIS", "file": cf.path}
                            )
                        return result

                # Run all analyzers in parallel for all files with semi-concurrency
                tasks = [analyze_with_limit(cf) for cf in files_to_analyze]
                results = await asyncio.gather(*tasks, return_exceptions=True)

                for cf, res in zip(files_to_analyze, results, strict=False):
                    if isinstance(res, Exception):
                        logger.error("file_analysis_failed", file=cf.path, error=str(res))
                        all_results[cf.path] = {}
                    else:
                        all_results[cf.path] = res
                        self._update_cache(cf, res)

                # Save cache after processing
                self._save_cache()

            # Aggregate results using MetricsAggregator
            metrics = self.metrics_aggregator.aggregate(all_results)

            # Calculate analysis duration
            metrics.analysis_duration = time.perf_counter() - start_time
            metrics.file_count = len(code_files)

            logger.info(
                "analysis_phase_completed",
                overall_score=metrics.overall_score,
                duration=metrics.analysis_duration,
                hotspots_found=len(metrics.hotspots),
                quick_wins_found=len(metrics.quick_wins),
            )

            # Notify progress callback with final score
            if self.progress_callback:
                self.progress_callback(
                    "analysis_completed",
                    {
                        "phase": "analysis",
                        "score": f"{metrics.overall_score:.1f}/10.0",
                        "duration": f"{metrics.analysis_duration:.2f}s",
                    },
                )

            return metrics

        except Exception as e:
            logger.error(
                "analysis_phase_failed",
                error=str(e),
            )
            # Return basic metrics on failure
            return QualityMetrics(
                overall_score=5.0,  # Default middle score
                analysis_duration=time.perf_counter() - start_time,
                file_count=len(code_files),
            )

    async def _analyze_file_async(self, code_file: CodeFile, pipeline_context: Any | None = None) -> dict[str, Any]:
        """
        Run all analyzers on a single file.

        Args:
            code_file: Code file to analyze
            pipeline_context: Optional pipeline context with cached ASTs

        Returns:
            Dictionary with analyzer results
        """
        # Create tasks for parallel execution
        tasks = {}

        # Get cached AST if available
        ast_tree = None
        if pipeline_context and hasattr(pipeline_context, "ast_cache"):
            ast_tree = pipeline_context.ast_cache.get(code_file.path)

        # Detect language
        language = get_language_from_path(code_file.path)
        is_python = language == CodeLanguage.PYTHON

        # Safety Check: If we have a cached AST but it's not Python-compatible (Tree-sitter node),
        # we must discard it for standard 'ast' based analyzers to prevent crashes.
        # PythonASTProvider stores ast.AST in raw_node.
        import ast

        if is_python and ast_tree is not None and not isinstance(ast_tree, ast.AST):
            logger.debug("incompatible_ast_cache_discarding", file=code_file.path, type=type(ast_tree).__name__)
            ast_tree = None

        # Subsetting analyzers for BASIC level to hit performance targets
        from warden.pipeline.domain.enums import AnalysisLevel

        is_basic = self.analysis_level == AnalysisLevel.BASIC

        # Core analyzers for scoring (Python-specific for now)
        if is_python:
            tasks["complexity"] = asyncio.create_task(
                self.analyzers["complexity"].analyze_async(code_file, ast_tree=ast_tree)
            )

            # Skip duplication check in BASIC level (heavy)
            if not is_basic:
                tasks["duplication"] = asyncio.create_task(
                    self.analyzers["duplication"].analyze_async(code_file, ast_tree=ast_tree)
                )

            tasks["maintainability"] = asyncio.create_task(
                self.analyzers["maintainability"].analyze_async(code_file, ast_tree=ast_tree)
            )
            tasks["naming"] = asyncio.create_task(self.analyzers["naming"].analyze_async(code_file, ast_tree=ast_tree))
            tasks["documentation"] = asyncio.create_task(
                self.analyzers["documentation"].analyze_async(code_file, ast_tree=ast_tree)
            )
            tasks["testability"] = asyncio.create_task(
                self.analyzers["testability"].analyze_async(code_file, ast_tree=ast_tree)
            )

            # Additional analyzer for hotspots
            tasks["magic_numbers"] = asyncio.create_task(
                self.analyzers["magic_numbers"].analyze_async(code_file, ast_tree=ast_tree)
            )

        # Skip LSP in BASIC level (slow/external dependency)
        if not is_basic:
            tasks["lsp_diagnostics"] = asyncio.create_task(
                self.analyzers["lsp_diagnostics"].analyze_async(code_file, ast_tree=ast_tree)
            )

        # Wait for all analyzers with timeout
        try:
            timeout = self.config.get("timeout", 15.0)
            results = await asyncio.wait_for(asyncio.gather(*tasks.values(), return_exceptions=True), timeout=timeout)

            # Map results back to analyzer names
            analyzer_results = {}
            for (name, _), result in zip(tasks.items(), results, strict=False):
                if isinstance(result, Exception):
                    logger.warning(
                        "analyzer_failed",
                        analyzer=name,
                        error=str(result),
                        file=code_file.path,
                    )
                    analyzer_results[name] = None
                else:
                    analyzer_results[name] = result

            return analyzer_results

        except asyncio.TimeoutError:
            logger.warning(
                "file_analysis_timeout",
                file=code_file.path,
                timeout=timeout,
            )
            return {}

    async def execute_with_llm_async(self, code_files: list[CodeFile]) -> QualityMetrics:
        """
        Execute analysis with LLM enhancement.

        Args:
            code_files: List of code files to analyze

        Returns:
            Enhanced QualityMetrics with LLM insights

        Note:
            This is a placeholder for future LLM integration.
            Will be implemented when LLM analyzer is added.
        """
        # First run standard analysis
        metrics = await self.execute_async(code_files)

        if self.config.get("use_llm", False):
            logger.info(
                "llm_metrics_enhancement_not_available",
                message="LLM-enhanced metrics analysis is planned but not yet implemented",
            )

        return metrics
