"""Analyst agent -- Self-awareness engine.

Knows everything about the codebase, docs, architecture, and runtime state.
Builds and maintains the codebase map. Detects evolution signals.

Scope boundary: Analyst READS everything but WRITES only to .hive/ files.
"""

from __future__ import annotations

import ast
import json
import logging
from datetime import datetime, timezone
from pathlib import Path
from typing import Any

from hive.agents.base import AgentRole, BaseAgent

logger = logging.getLogger("hive.analyst")

# File extensions to index
PYTHON_EXTENSIONS = {".py"}
CONFIG_EXTENSIONS = {".toml", ".yaml", ".yml", ".json", ".cfg", ".ini"}
DOC_EXTENSIONS = {".md", ".rst", ".txt"}
IGNORE_DIRS = {"__pycache__", ".git", ".mypy_cache", ".pytest_cache", ".ruff_cache", "node_modules", ".hive"}


class Analyst(BaseAgent):
    """Self-awareness engine -- knows the entire codebase.

    Responsibilities:
    - Index all source files into codebase-map.json
    - Detect architectural drift (code vs docs)
    - Detect evolution signals (pivot indicators)
    - Provide context to other agents on request
    """

    role = AgentRole.ANALYST

    def __init__(self, workspace_root: Path) -> None:
        super().__init__(workspace_root)
        self._codebase_map: dict[str, Any] = {}

    def index_codebase(self) -> dict[str, Any]:
        """Full codebase index -- builds codebase-map.json.

        Walks the workspace, indexes Python files at AST level,
        other files at file level. Builds dependency graph.
        """
        logger.info("Starting full codebase index at %s", self._workspace_root)

        modules: list[dict[str, Any]] = []
        total_lines = 0
        languages: dict[str, int] = {}

        for filepath in self._workspace_root.rglob("*"):
            if not filepath.is_file():
                continue
            if any(ignored in filepath.parts for ignored in IGNORE_DIRS):
                continue

            relative = filepath.relative_to(self._workspace_root)
            ext = filepath.suffix.lower()

            if ext in PYTHON_EXTENSIONS:
                module_info = self._index_python_file(filepath, relative)
                if module_info:
                    modules.append(module_info)
                    total_lines += module_info.get("lines", 0)
                    languages["python"] = languages.get("python", 0) + 1
            elif ext in CONFIG_EXTENSIONS:
                languages["config"] = languages.get("config", 0) + 1
            elif ext in DOC_EXTENSIONS:
                languages["markdown"] = languages.get("markdown", 0) + 1

        # Build dependency graph
        dependency_graph = self._build_dependency_graph(modules)

        # Identify hot files (most imported)
        import_counts: dict[str, int] = {}
        for module in modules:
            for imp in module.get("imports", []):
                if imp.startswith("llmhosts") or imp.startswith("hive"):
                    import_counts[imp] = import_counts.get(imp, 0) + 1

        hot_files = sorted(import_counts.items(), key=lambda x: x[1], reverse=True)[:10]

        self._codebase_map = {
            "version": "1.0",
            "generated": datetime.now(timezone.utc).isoformat(),
            "stats": {
                "total_files": len(modules),
                "total_lines": total_lines,
                "languages": languages,
            },
            "modules": modules,
            "dependency_graph": dependency_graph,
            "hot_files": [{"module": m, "import_count": c} for m, c in hot_files],
        }

        # Write to .hive/codebase-map.json
        map_path = self._hive_dir / "codebase-map.json"
        map_path.write_text(json.dumps(self._codebase_map, indent=2), encoding="utf-8")

        logger.info(
            "Codebase indexed: %d files, %d lines, %d Python modules",
            len(modules),
            total_lines,
            languages.get("python", 0),
        )

        return self._codebase_map

    def _index_python_file(self, filepath: Path, relative: Path) -> dict[str, Any] | None:
        """Index a Python file at AST level."""
        try:
            source = filepath.read_text(encoding="utf-8")
            tree = ast.parse(source, filename=str(relative))
        except (SyntaxError, UnicodeDecodeError) as e:
            logger.warning("Failed to parse %s: %s", relative, e)
            return None

        imports: list[str] = []
        exports: list[dict[str, Any]] = []

        for node in ast.walk(tree):
            if isinstance(node, ast.Import):
                for alias in node.names:
                    imports.append(alias.name)
            elif isinstance(node, ast.ImportFrom) and node.module:
                imports.append(node.module)

        for node in ast.iter_child_nodes(tree):
            if isinstance(node, ast.ClassDef):
                methods = [m.name for m in node.body if isinstance(m, (ast.FunctionDef, ast.AsyncFunctionDef))]
                exports.append(
                    {
                        "type": "class",
                        "name": node.name,
                        "methods": methods,
                        "line_start": node.lineno,
                        "line_end": node.end_lineno or node.lineno,
                    }
                )
            elif isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)):
                exports.append(
                    {
                        "type": "function",
                        "name": node.name,
                        "params": [arg.arg for arg in node.args.args],
                        "line_start": node.lineno,
                        "line_end": node.end_lineno or node.lineno,
                    }
                )

        line_count = source.count("\n") + 1

        return {
            "path": str(relative),
            "type": "module",
            "imports": imports,
            "exports": exports,
            "lines": line_count,
            "last_modified": datetime.fromtimestamp(filepath.stat().st_mtime, tz=timezone.utc).isoformat(),
        }

    def _build_dependency_graph(self, modules: list[dict[str, Any]]) -> dict[str, list[str]]:
        """Build a module dependency graph from import data."""
        graph: dict[str, list[str]] = {}

        for module in modules:
            module_path = module["path"]
            # Convert file path to module name
            module_name = module_path.replace("/", ".").replace(".py", "")
            if module_name.endswith(".__init__"):
                module_name = module_name[: -len(".__init__")]

            internal_deps: list[str] = []
            for imp in module.get("imports", []):
                if imp.startswith("llmhosts") or imp.startswith("hive"):
                    internal_deps.append(imp)

            if internal_deps:
                graph[module_name] = internal_deps

        return graph

    def detect_drift(self) -> list[dict[str, str]]:
        """Detect drift between code and documentation.

        Returns a list of drift alerts.
        """
        alerts: list[dict[str, str]] = []

        # Check for Python files without corresponding test files
        for module in self._codebase_map.get("modules", []):
            path = module["path"]
            if path.startswith("llmhosts/") and not path.startswith("tests/"):
                test_path = "tests/test_" + Path(path).name
                test_exists = any(m["path"] == test_path for m in self._codebase_map.get("modules", []))
                if not test_exists and module.get("exports"):
                    alerts.append(
                        {
                            "type": "test_coverage_drift",
                            "file": path,
                            "message": f"No test file found for {path}",
                        }
                    )

        return alerts

    def get_module_context(self, module_path: str) -> dict[str, Any] | None:
        """Get detailed context about a specific module.

        Used by other agents when they need codebase context.
        """
        for module in self._codebase_map.get("modules", []):
            if module["path"] == module_path:
                return dict(module)
        return None

    def get_status(self) -> dict[str, Any]:
        """Analyst-specific status."""
        status = super().get_status()
        stats = self._codebase_map.get("stats", {})
        status.update(
            {
                "indexed_files": stats.get("total_files", 0),
                "indexed_lines": stats.get("total_lines", 0),
                "last_indexed": self._codebase_map.get("generated", "never"),
            }
        )
        return status
