"""Analysis orchestrator that ties all pipeline stages together.

The main entry point is analyze_repository() which runs the complete
static analysis pipeline and returns an AnalysisResult for LLM consumption.
"""

from __future__ import annotations

import logging
from pathlib import Path
from typing import TYPE_CHECKING

from unfamiliar.analysis.adapters import create_default_registry
from unfamiliar.analysis.discovery import discover_files
from unfamiliar.analysis.filters.patterns import FileFilter
from unfamiliar.analysis.graph.clustering import detect_modules
from unfamiliar.analysis.graph.import_graph import build_import_graph
from unfamiliar.analysis.models import AnalysisResult, SecurityResult
from unfamiliar.analysis.scoring import score_files
from unfamiliar.analysis.security import scan_for_secrets, scan_vulnerabilities
from unfamiliar.analysis.testing import detect_tests
from unfamiliar.config import load_repo_filter_config
from unfamiliar.constants import DEFAULT_TOKEN_BUDGET

if TYPE_CHECKING:
    from unfamiliar.analysis.models import FileInfo, ParsedFile

logger = logging.getLogger(__name__)


def analyze_repository(
    repo_path: Path,
    token_budget: int = DEFAULT_TOKEN_BUDGET,
) -> AnalysisResult:
    """Run the complete static analysis pipeline on a repository.

    Pipeline stages:
    1. Create adapter registry
    2. Discover files (git ls-files, hashing, language classification, filtering)
    3. Parse files with language adapters
    4. Collect entry points
    5. Build import graph
    6. Detect modules via clustering
    7. Detect tests and coverage
    8. Score files for priority
    9. Security scanning (secrets, vulnerabilities)

    Args:
        repo_path: Path to the git repository root.
        token_budget: Token budget for key file selection.

    Returns:
        Complete AnalysisResult with all pipeline outputs.

    Raises:
        AnalysisError: If repo_path is not a valid git repo.
    """
    logger.info("Starting analysis of %s", repo_path)

    # Stage 1: Create adapter registry
    registry = create_default_registry()
    logger.debug(
        "Created registry with adapters: %s",
        list(registry.supported_languages().keys()),
    )

    # Stage 2: Discover files with filtering
    # Load per-repo filter config and create filter
    filter_config = load_repo_filter_config(repo_path)
    file_filter = FileFilter(
        include_patterns=filter_config.include_patterns,
        exclude_patterns=filter_config.exclude_patterns,
    )
    file_filter.load_gitignore(repo_path)

    files, excluded_count = discover_files(repo_path, registry, file_filter)
    logger.info(
        "Discovered %d files (%d excluded by filter)",
        len(files),
        excluded_count,
    )

    if not files:
        logger.warning("No files discovered in repository")
        return AnalysisResult(repo_path=str(repo_path), excluded_count=excluded_count)

    # Build lookup dicts
    file_infos: dict[str, FileInfo] = {f.path: f for f in files}
    repo_file_paths = set(file_infos.keys())

    # Stage 3: Parse files with adapters
    parsed_files: dict[str, ParsedFile] = {}
    for file_info in files:
        adapter = registry.get_adapter(file_info.path)
        if adapter is None:
            continue

        file_path = repo_path / file_info.path
        try:
            content = file_path.read_bytes()
        except OSError as e:
            logger.warning("Could not read %s: %s", file_info.path, e)
            continue

        parsed = adapter.parse_file(Path(file_info.path), content)
        parsed_files[file_info.path] = parsed

    logger.info("Parsed %d files", len(parsed_files))

    # Stage 4: Collect entry points
    entry_point_files = {
        str(ep.file_path)
        for parsed in parsed_files.values()
        for ep in parsed.entry_points
    }
    logger.info("Found %d entry points", len(entry_point_files))

    # Stage 5: Build import graph
    import_graph = build_import_graph(
        parsed_files=parsed_files,
        file_infos=file_infos,
        registry=registry,
        repo_files=repo_file_paths,
    )
    logger.info(
        "Built import graph with %d edges",
        sum(len(targets) for targets in import_graph.edges.values()),
    )

    # Stage 6: Detect modules
    modules = detect_modules(import_graph)
    logger.info("Detected %d modules", len(modules))

    # Stage 7: Detect tests
    test_info = detect_tests(files, parsed_files, repo_path)
    logger.info(
        "Detected %d test files, %d untested files",
        len(test_info.test_files),
        len(test_info.untested_files),
    )

    # Stage 8: Score files
    scored_files = score_files(
        files=files,
        parsed_files=parsed_files,
        graph=import_graph,
        test_info=test_info,
        entry_points=entry_point_files,
    )

    # Compute language distribution
    languages: dict[str, int] = {}
    for file_info in files:
        lang = file_info.language
        languages[lang] = languages.get(lang, 0) + 1

    # Collect all EntryPoint objects for result
    entry_point_objects = [
        ep for parsed in parsed_files.values() for ep in parsed.entry_points
    ]

    # Stage 9: Security scanning
    security_result = SecurityResult()

    # Scan for secrets
    file_paths = [f.path for f in files]
    secrets = scan_for_secrets(file_paths, repo_path)
    security_result.secrets = secrets
    logger.info("Found %d potential secrets", len(secrets))

    # Scan for vulnerabilities
    vulns, manifests, errors = scan_vulnerabilities(repo_path)
    security_result.vulnerabilities = vulns
    security_result.manifests_scanned = manifests
    security_result.scan_errors = errors
    logger.info(
        "Found %d vulnerabilities in %d manifests",
        len(vulns),
        len(manifests),
    )
    if errors:
        logger.warning("Security scan errors: %s", errors)

    return AnalysisResult(
        files=files,
        parsed_files=parsed_files,
        import_graph=import_graph,
        modules=modules,
        test_info=test_info,
        scored_files=scored_files,
        entry_points=entry_point_objects,
        languages=languages,
        repo_path=str(repo_path),
        security=security_result,
        excluded_count=excluded_count,
    )
