"""Unified agent interface for agentic AI interactions.

Provides a single entry point for AI agents to query across all three
subsystems (code, docs, telemetry) with smart routing and context assembly.
"""

from __future__ import annotations

import logging
from dataclasses import dataclass, field
from typing import Any

from common.context import AssembledContext, ContextAssembler, route_query

logger = logging.getLogger(__name__)


@dataclass(slots=True)
class ComponentContext:
    """Everything known about a component: code, docs, recent telemetry."""
    component_name: str
    code_entities: list[dict[str, Any]] = field(default_factory=list)
    doc_chunks: list[dict[str, Any]] = field(default_factory=list)
    recent_logs: list[dict[str, Any]] = field(default_factory=list)
    recent_traces: list[dict[str, Any]] = field(default_factory=list)
    metrics: list[dict[str, Any]] = field(default_factory=list)
    graph_neighborhood: dict[str, Any] = field(default_factory=dict)


@dataclass(slots=True)
class IncidentContext:
    """Context assembled for incident investigation."""
    error_or_trace_id: str
    trace: dict[str, Any] = field(default_factory=dict)
    related_code: list[dict[str, Any]] = field(default_factory=list)
    relevant_docs: list[dict[str, Any]] = field(default_factory=list)
    similar_past_incidents: list[dict[str, Any]] = field(default_factory=list)


class AgentInterface:
    """Unified interface for agentic AI to interact with all 3 subsystems.

    Wraps ContextAssembler with higher-level query patterns.
    """

    def __init__(
        self,
        assembler: ContextAssembler,
        code_repo=None,
        docs_repo=None,
        otel_repo=None,
    ) -> None:
        self.assembler = assembler
        self.code_repo = code_repo or assembler.code_repo
        self.docs_repo = docs_repo or assembler.docs_repo
        self.otel_repo = otel_repo or assembler.otel_repo

    def query(self, question: str, budget: int = 8000) -> AssembledContext:
        """Smart routing + context assembly for a natural language question."""
        return self.assembler.assemble(question, context_budget=budget)

    def search_code(
        self, query: str, version: str | None = None, limit: int = 20,
    ) -> list[dict[str, Any]]:
        """Search code entities by name or pattern."""
        if self.code_repo is None:
            return []
        return self.code_repo.search_entities(query, limit=limit)

    def search_docs(
        self, query: str, limit: int = 10,
    ) -> list[dict[str, Any]]:
        """Search document chunks."""
        if self.docs_repo is None:
            return []
        try:
            return self.docs_repo.search(query, limit=limit)
        except Exception:
            return []

    def search_telemetry(
        self, query: str, limit: int = 10,
    ) -> list[dict[str, Any]]:
        """Search logs and traces."""
        if self.otel_repo is None:
            return []
        results = []
        try:
            results.extend(self.otel_repo.search_logs(query, limit=limit))
        except Exception:
            pass
        try:
            results.extend(self.otel_repo.search_traces(query, limit=limit))
        except Exception:
            pass
        return results

    def get_component_overview(self, component_name: str) -> ComponentContext:
        """Everything about a component: code, docs, recent telemetry."""
        ctx = ComponentContext(component_name=component_name)

        if self.code_repo:
            try:
                ctx.code_entities = self.code_repo.search_entities(component_name, limit=20)
            except Exception:
                pass

        if self.docs_repo:
            try:
                ctx.doc_chunks = self.docs_repo.search(component_name, limit=10)
            except Exception:
                pass

        if self.otel_repo:
            try:
                ctx.recent_logs = self.otel_repo.search_logs(component_name, limit=10)
            except Exception:
                pass
            try:
                ctx.recent_traces = self.otel_repo.search_traces(component_name, limit=10)
            except Exception:
                pass

        if self.assembler.graph_store and self.assembler.graph_store.has_node(component_name):
            ctx.graph_neighborhood = self.assembler.graph_store.neighborhood(
                component_name, depth=2, max_nodes=50,
            )

        return ctx

    def get_incident_context(self, trace_id_or_error: str) -> IncidentContext:
        """Error trace -> related code -> relevant docs -> similar past incidents."""
        ctx = IncidentContext(error_or_trace_id=trace_id_or_error)

        if self.otel_repo:
            try:
                trace = self.otel_repo.get_trace(trace_id_or_error)
                if trace:
                    ctx.trace = trace
            except Exception:
                pass

            # Search for similar log patterns
            try:
                ctx.similar_past_incidents = self.otel_repo.search_logs(
                    trace_id_or_error, limit=10,
                )
            except Exception:
                pass

        # Find related code
        service_name = ctx.trace.get("root_service_name", trace_id_or_error)
        if self.code_repo:
            try:
                ctx.related_code = self.code_repo.search_entities(service_name, limit=10)
            except Exception:
                pass

        # Find relevant docs
        if self.docs_repo:
            try:
                ctx.relevant_docs = self.docs_repo.search(service_name, limit=5)
            except Exception:
                pass

        return ctx

    def diff_versions(self, old_version: str, new_version: str | None = None) -> dict[str, Any]:
        """Show what changed between code versions."""
        if self.code_repo is None:
            return {"error": "Code repository not available"}
        changes = self.code_repo.get_changed_files(old_version)
        return {
            "old_version": old_version,
            "new_version": new_version or "latest",
            "changes": changes,
            "total": len(changes),
        }
