"""
Enhanced pipeline orchestrator with discovery, build context, and suppression.

Extends the base PipelineOrchestrator with optional pre/post-processing phases:
- Pre: File discovery (finds all project files)
- Pre: Build context loading (extracts dependency info)
- Post: Suppression filtering (removes false positives)
"""

from pathlib import Path
from typing import Any

from warden.analysis.application.discovery import DiscoveredFile, FileDiscoverer
from warden.build_context import BuildContext, BuildContextProvider
from warden.pipeline.application.orchestrator import PhaseOrchestrator
from warden.pipeline.domain.models import (
    PipelineConfig,
    PipelineResult,
)
from warden.shared.infrastructure.logging import get_logger
from warden.validation.domain.frame import CodeFile, ValidationFrame

logger = get_logger(__name__)


class EnhancedPipelineOrchestrator(PhaseOrchestrator):
    """
    Enhanced pipeline orchestrator with optional discovery, build context, and suppression.

    This orchestrator extends the base pipeline with three optional phases:

    1. **Discovery Phase** (Pre-validation):
       - Scans project directory for code files
       - Respects .gitignore patterns
       - Filters by file type
       - Only runs if enable_discovery=True

    2. **Build Context Phase** (Pre-validation):
       - Loads build configuration (package.json, pyproject.toml, etc.)
       - Extracts dependency information
       - Makes build context available to frames
       - Only runs if enable_build_context=True



    All phases are optional and controlled by PipelineConfig flags.
    """

    def __init__(
        self,
        frames: list[ValidationFrame],
        config: PipelineConfig | None = None,
    ) -> None:
        """
        Initialize enhanced orchestrator.

        Args:
            frames: List of validation frames to execute
            config: Pipeline configuration (with optional phase flags)
        """
        super().__init__(frames, config)

        # Phase tracking
        self.discovery_result: Any | None = None
        self.build_context: BuildContext | None = None

        logger.info(
            "enhanced_orchestrator_initialized",
            frame_count=len(frames),
            discovery_enabled=self.config.enable_discovery,
            build_context_enabled=self.config.enable_build_context,
        )

    async def execute_with_discovery_async(self, project_path: str) -> PipelineResult:
        """
        Execute pipeline with automatic file discovery.

        This is the main entry point for the enhanced pipeline. It:
        1. Discovers files in the project (if enabled)
        2. Loads build context (if enabled)
        3. Converts discovered files to CodeFile objects
        4. Runs validation frames on all files


        Args:
            project_path: Root path of the project to analyze

        Returns:
            PipelineResult with aggregated findings

        Example:
            >>> orchestrator = EnhancedPipelineOrchestrator(frames=[SecurityFrame()])
            >>> result = await orchestrator.execute_with_discovery_async("/path/to/project")
            >>> print(f"Total findings: {result.total_findings}")
        """
        logger.info(
            "enhanced_pipeline_started",
            project_path=project_path,
        )

        # Phase 1: Discovery (if enabled)
        code_files = await self._discover_files_phase_async(project_path)

        # Phase 2: Build context (if enabled)
        await self._load_build_context_phase_async(project_path)

        # Phase 3: Run validation frames
        result = await super().execute_async(code_files)

        logger.info(
            "enhanced_pipeline_completed",
            project_path=project_path,
            total_files=len(code_files),
            total_findings=result.total_findings,
        )

        return result

    async def _discover_files_phase_async(self, project_path: str) -> list[CodeFile]:
        """
        Phase 1: Discover files in project.

        Args:
            project_path: Root path of project

        Returns:
            List of CodeFile objects to analyze
        """
        if not self.config.enable_discovery:
            logger.info("discovery_phase_skipped", reason="disabled_in_config")
            # Return empty list - caller must provide files manually
            return []

        logger.info("discovery_phase_started", project_path=project_path)

        try:
            # Get discovery configuration
            discovery_config = self.config.discovery_config or {}
            max_depth = discovery_config.get("max_depth")
            use_gitignore = discovery_config.get("use_gitignore", True)
            max_size_mb = discovery_config.get("max_size_mb")

            # Fallback to global rules for size limit if not explicitly in discovery_config
            if max_size_mb is None and self.config.global_rules:
                for rule in self.config.global_rules:
                    if rule.id == "file-size-limit" and rule.enabled:
                        max_size_mb = rule.conditions.get("max_size_mb")
                        if max_size_mb:
                            logger.info(
                                "discovery_limit_extracted_from_global_rules", rule_id=rule.id, limit_mb=max_size_mb
                            )
                            break

            # Create discoverer
            logger.info("discovery_parameters", max_size_mb=max_size_mb, use_gitignore=use_gitignore)
            discoverer = FileDiscoverer(
                root_path=project_path,
                max_depth=max_depth,
                use_gitignore=use_gitignore,
                max_size_mb=max_size_mb,
            )

            # Run discovery
            self.discovery_result = await discoverer.discover_async()

            # Convert to CodeFile objects
            code_files = await self._convert_to_code_files_async(self.discovery_result.get_analyzable_files())

            logger.info(
                "discovery_phase_completed",
                total_files=self.discovery_result.stats.total_files,
                analyzable_files=len(code_files),
                duration=f"{self.discovery_result.stats.scan_duration_seconds:.2f}s",
            )

            return code_files

        except Exception as e:
            logger.error(
                "discovery_phase_failed",
                project_path=project_path,
                error=str(e),
            )
            # Return empty list on error
            return []

    async def _convert_to_code_files_async(self, discovered_files: list[DiscoveredFile]) -> list[CodeFile]:
        """
        Convert DiscoveredFile objects to CodeFile objects.

        Args:
            discovered_files: List of discovered files

        Returns:
            List of CodeFile objects with loaded content
        """
        code_files: list[CodeFile] = []

        for discovered_file in discovered_files:
            try:
                # Read file content
                file_path = Path(discovered_file.path)
                content = file_path.read_text(encoding="utf-8")

                # Determine language from file extension
                language = self._get_language_from_extension(file_path.suffix)

                # Create CodeFile
                code_file = CodeFile(
                    path=str(file_path),
                    content=content,
                    language=language,
                    framework=None,  # Framework detection happens at project level
                    size_bytes=discovered_file.size_bytes,
                )

                code_files.append(code_file)

            except Exception as e:
                logger.warning(
                    "file_load_failed",
                    file_path=discovered_file.path,
                    error=str(e),
                )
                continue

        return code_files

    def _get_language_from_extension(self, extension: str) -> str:
        """
        Get language name from file extension.

        Args:
            extension: File extension (e.g., '.py', '.js')

        Returns:
            Language name
        """
        from warden.shared.utils.language_utils import get_language_from_path

        # get_language_from_path expects a Path or string with extension
        # If we only have extension, we can create a dummy path
        return get_language_from_path(f"dummy{extension}").value

    async def _load_build_context_phase_async(self, project_path: str) -> None:
        """
        Phase 2: Load build context from project.

        Args:
            project_path: Root path of project
        """
        if not self.config.enable_build_context:
            logger.info("build_context_phase_skipped", reason="disabled_in_config")
            return

        logger.info("build_context_phase_started", project_path=project_path)

        try:
            # Create build context provider
            provider = BuildContextProvider(project_path)

            # Load context asynchronously
            self.build_context = await provider.get_context_async()

            logger.info(
                "build_context_phase_completed",
                build_system=self.build_context.build_system.value,
                project_name=self.build_context.project_name,
                dependency_count=len(self.build_context.dependencies),
            )

        except Exception as e:
            logger.warning(
                "build_context_phase_failed",
                project_path=project_path,
                error=str(e),
            )
            # Continue without build context
            self.build_context = None

    def _extract_line_number(self, location: str) -> int:
        """
        Extract line number from location string.

        Args:
            location: Location string (format: "path:line" or "path:line:col")

        Returns:
            Line number (1-indexed), or 1 if cannot parse
        """
        try:
            parts = location.split(":")
            if len(parts) >= 2:
                return int(parts[1])
        except (ValueError, IndexError):
            pass

        return 1  # Default to line 1

    def _extract_file_path(self, location: str) -> str:
        """
        Extract file path from location string.

        Args:
            location: Location string (format: "path:line" or "path:line:col")

        Returns:
            File path
        """
        parts = location.split(":")
        if parts:
            return parts[0]
        return ""

    def get_discovery_result(self) -> Any | None:
        """
        Get the discovery result from the last execution.

        Returns:
            DiscoveryResult or None if discovery was not run
        """
        return self.discovery_result

    def get_build_context(self) -> BuildContext | None:
        """
        Get the build context from the last execution.

        Returns:
            BuildContext or None if build context was not loaded
        """
        return self.build_context
