"""Specialist-based code review system.

Routes file analysis to specialized analyzers that use deterministic AST/grep
checks with LLM micro-calls only at ambiguous decision points.
"""

from __future__ import annotations

import asyncio
import os
import time
from typing import Optional

from autodebug.agent.specialists.base import ASTData, Finding, Specialist


def _get_registry() -> list[Specialist]:
    """Lazy-load specialists to avoid circular imports."""
    from autodebug.agent.specialists.swallowed_exception import SwallowedExceptionSpecialist
    from autodebug.agent.specialists.dead_code import DeadCodeSpecialist
    from autodebug.agent.specialists.none_return import NoneReturnSpecialist
    from autodebug.agent.specialists.command_injection import CommandInjectionSpecialist
    from autodebug.agent.specialists.path_traversal import PathTraversalSpecialist
    from autodebug.agent.specialists.missing_await import MissingAwaitSpecialist
    from autodebug.agent.specialists.resource_leak import ResourceLeakSpecialist
    from autodebug.agent.specialists.unsafe_shell import UnsafeShellSpecialist
    from autodebug.agent.specialists.sql_injection import SQLInjectionSpecialist
    from autodebug.agent.specialists.unsafe_defaults import UnsafeDefaultsSpecialist
    from autodebug.agent.specialists.none_check import NoneCheckSpecialist
    from autodebug.agent.specialists.hardcoded_secrets import HardcodedSecretsSpecialist
    from autodebug.agent.specialists.unreachable_code import UnreachableCodeSpecialist
    from autodebug.agent.specialists.common_bugs import CommonBugsSpecialist

    return [
        SwallowedExceptionSpecialist(),
        DeadCodeSpecialist(),
        NoneReturnSpecialist(),
        CommandInjectionSpecialist(),
        PathTraversalSpecialist(),
        MissingAwaitSpecialist(),
        ResourceLeakSpecialist(),
        UnsafeShellSpecialist(),
        SQLInjectionSpecialist(),
        UnsafeDefaultsSpecialist(),
        NoneCheckSpecialist(),
        HardcodedSecretsSpecialist(),
        UnreachableCodeSpecialist(),
        CommonBugsSpecialist(),
    ]


async def run_specialists(
    file_path: str,
    project_path: str,
    focus: str = "bugs, security issues, code quality",
) -> tuple[list[Finding], dict[str, float]]:
    """Run all matching specialists on a file.

    Returns (findings, timing_dict) where timing maps specialist name to seconds.
    """
    full_path = os.path.join(project_path, file_path)
    try:
        source = open(full_path, encoding="utf-8", errors="replace").read()
    except FileNotFoundError:
        return [], {"error": 0.0, "_msg": f"file not found: {full_path}"}
    except Exception as e:
        return [], {"error": 0.0, "_msg": str(e)}

    try:
        ast_data = ASTData.from_source(source)
    except SyntaxError:
        return [], {"ast_parse_error": 0.0}

    registry = _get_registry()
    matching = [s for s in registry if s.should_run(ast_data, focus)]

    if not matching:
        return [], {}

    timing: dict[str, float] = {}

    async def _run_one(specialist: Specialist) -> list[Finding]:
        t0 = time.time()
        try:
            findings = await asyncio.wait_for(
                specialist.run(source, ast_data),
                timeout=4.0,
            )
            timing[specialist.name] = time.time() - t0
            return findings
        except asyncio.TimeoutError:
            timing[specialist.name] = time.time() - t0
            return []
        except Exception:
            timing[specialist.name] = time.time() - t0
            return []

    results = await asyncio.gather(*[_run_one(s) for s in matching])

    all_findings: list[Finding] = []
    for findings_list in results:
        all_findings.extend(findings_list)

    return all_findings, timing
