"""Agent builder for creating custom specialized agents with historical context."""

from __future__ import annotations

import json
from collections import Counter
from dataclasses import dataclass, field
from pathlib import Path
from typing import Any, Optional

from rich.console import Console
from rich.progress import Progress, SpinnerColumn, TextColumn
from rich.prompt import Prompt

from emdash.agent.toolkit import AgentToolkit
from emdash.agent.providers import get_provider
from emdash.agent.providers.factory import DEFAULT_MODEL
from emdash.graph.connection import get_connection
from emdash.utils.logger import log


@dataclass
class AgentBuilderContext:
    """Gathered context for building a custom agent."""

    name: str
    purpose: str
    related_prs: list[dict] = field(default_factory=list)
    review_patterns: list[dict] = field(default_factory=list)
    domain_experts: list[dict] = field(default_factory=list)
    code_communities: list[dict] = field(default_factory=list)
    relevant_files: list[str] = field(default_factory=list)
    things_to_avoid: list[dict] = field(default_factory=list)
    hints: list[str] = field(default_factory=list)
    selected_mcp_tools: list[str] = field(default_factory=list)


AVAILABLE_MCP_TOOLS = [
    ("github_search_code", "Search code patterns on GitHub"),
    ("github_pr_details", "Get PR details, diff, and reviews"),
    ("github_list_prs", "List PRs in repository"),
    ("github_search_prs", "Search PRs by text"),
    ("github_get_file_content", "Read files from GitHub"),
    ("github_get_issue", "Get issue details"),
    ("github_create_review", "Create PR reviews"),
    ("semantic_search", "Semantic code search in graph"),
    ("expand_node", "Expand code context in graph"),
    ("get_callers", "Find callers of a function"),
    ("get_callees", "Find callees of a function"),
]


SYNTHESIS_PROMPT = """You are synthesizing knowledge from a codebase to create actionable hints for a specialized agent.

Based on the following data from PRs, reviews, and code patterns, generate 5-10 specific, actionable best practice hints.

RULES:
- Extract patterns from actual review comments and PR discussions
- Focus on specific, actionable advice (not generic "write good code")
- Reference specific patterns or practices from the codebase
- Each hint should be 1-2 sentences maximum
- Return ONLY a JSON array of strings, no other text

EXAMPLE OUTPUT:
["Always include unit tests when modifying API endpoints", "Use the existing ErrorHandler class for error responses", "Check for breaking changes when updating shared types"]

DATA:
{data}

OUTPUT (JSON array only):"""


class AgentBuilderAgent:
    """Agent that builds custom specialized agents by gathering knowledge from the codebase."""

    def __init__(
        self,
        model: str = DEFAULT_MODEL,
        verbose: bool = True,
    ):
        self.provider = get_provider(model)
        self.toolkit = AgentToolkit(enable_session=False)
        self.model = model
        self.verbose = verbose
        self.console = Console()

        # Graph connection for Kuzu queries
        try:
            self.graph = get_connection()
        except Exception:
            self.graph = None
            log.warning("Graph connection not available - community analysis will be limited")

    def build(
        self,
        agent_name: str,
        purpose: str,
        max_prs: int = 50,
        interactive: bool = True,
    ) -> AgentBuilderContext:
        """Build a custom agent by gathering knowledge from the codebase.

        Args:
            agent_name: Name for the new agent
            purpose: What the agent should specialize in
            max_prs: Maximum PRs to analyze
            interactive: Whether to prompt for MCP tool selection

        Returns:
            AgentBuilderContext with all gathered information
        """
        context = AgentBuilderContext(name=agent_name, purpose=purpose)

        with Progress(
            SpinnerColumn(),
            TextColumn("[progress.description]{task.description}"),
            console=self.console,
            disable=not self.verbose,
        ) as progress:
            # Phase 1: Search for related PRs
            task = progress.add_task("Searching for related PRs...", total=None)
            context.related_prs = self._search_related_prs(purpose, max_prs)
            progress.update(task, description=f"Found {len(context.related_prs)} related PRs")

            # Phase 2: Extract review patterns and anti-patterns
            progress.update(task, description="Extracting review patterns...")
            context.review_patterns, context.things_to_avoid = self._extract_review_patterns(
                context.related_prs
            )
            progress.update(
                task,
                description=f"Found {len(context.review_patterns)} patterns, {len(context.things_to_avoid)} anti-patterns"
            )

            # Phase 3: Find domain experts
            progress.update(task, description="Identifying domain experts...")
            context.domain_experts = self._find_domain_experts(context.related_prs)
            progress.update(task, description=f"Found {len(context.domain_experts)} domain experts")

            # Phase 4: Find relevant code communities
            progress.update(task, description="Finding code communities...")
            context.code_communities = self._find_relevant_communities(purpose)

            # Phase 5: Collect relevant files
            progress.update(task, description="Collecting relevant files...")
            context.relevant_files = self._collect_relevant_files(context)
            progress.update(task, description=f"Found {len(context.relevant_files)} relevant files")

            # Phase 6: Synthesize hints using LLM
            progress.update(task, description="Synthesizing hints...")
            context.hints = self._synthesize_hints(context)

            progress.update(task, description="Done gathering context!")

        # Interactive MCP tool selection
        if interactive:
            context.selected_mcp_tools = self._select_mcp_tools()
        else:
            # Default to common tools
            context.selected_mcp_tools = [
                "github_search_prs",
                "github_pr_details",
                "semantic_search",
                "expand_node",
            ]

        return context

    def _search_related_prs(self, purpose: str, max_prs: int) -> list[dict]:
        """Search for PRs related to the agent's purpose.

        Args:
            purpose: The agent's specialization purpose
            max_prs: Maximum PRs to return

        Returns:
            List of PR data dictionaries
        """
        # First try searching by purpose keywords
        result = self.toolkit.execute(
            "github_search_prs",
            query=purpose,
            per_page=min(100, max_prs),
        )

        prs = []
        if result.success:
            prs = result.data.get("prs", [])

        # If few results, also get recent PRs
        if len(prs) < max_prs // 2:
            for state in ["closed", "open"]:
                list_result = self.toolkit.execute(
                    "github_list_prs",
                    state=state,
                    per_page=min(50, max_prs - len(prs)),
                )
                if list_result.success:
                    prs.extend(list_result.data.get("prs", []))
                    if len(prs) >= max_prs:
                        break

        return prs[:max_prs]

    def _extract_review_patterns(
        self,
        prs: list[dict],
    ) -> tuple[list[dict], list[dict]]:
        """Extract positive patterns and anti-patterns from PR reviews.

        Args:
            prs: List of PR data dictionaries

        Returns:
            Tuple of (positive_patterns, anti_patterns)
        """
        patterns = []
        anti_patterns = []

        # Analyze top PRs with reviews
        for pr in prs[:20]:
            pr_num = pr.get("number")
            if not pr_num:
                continue

            # Get PR details with reviews
            details = self.toolkit.execute(
                "github_pr_details",
                pull_number=pr_num,
                include_diff=False,
                include_comments=False,
                include_reviews=True,
                include_review_comments=True,
            )

            if not details.success:
                continue

            # Extract from reviews
            reviews = details.data.get("reviews", [])
            for review in reviews:
                if not isinstance(review, dict):
                    continue

                state = review.get("state")
                body = review.get("body", "")
                user = review.get("user", {})
                reviewer = user.get("login", "unknown") if isinstance(user, dict) else "unknown"

                if state == "CHANGES_REQUESTED" and body:
                    anti_patterns.append({
                        "pr": pr_num,
                        "reviewer": reviewer,
                        "feedback": body[:500],
                        "type": "changes_requested",
                    })
                elif state == "APPROVED" and body:
                    patterns.append({
                        "pr": pr_num,
                        "reviewer": reviewer,
                        "feedback": body[:500],
                        "type": "approved",
                    })

            # Extract from inline review comments
            review_comments = details.data.get("review_comments", [])
            for comment in review_comments[:10]:
                if not isinstance(comment, dict):
                    continue

                body = comment.get("body", "")
                if not body:
                    continue

                user = comment.get("user", {})
                reviewer = user.get("login", "unknown") if isinstance(user, dict) else "unknown"

                patterns.append({
                    "pr": pr_num,
                    "path": comment.get("path"),
                    "reviewer": reviewer,
                    "feedback": body[:300],
                    "type": "inline",
                })

        return patterns, anti_patterns

    def _find_domain_experts(self, prs: list[dict]) -> list[dict]:
        """Find authors and reviewers with most activity in related PRs.

        Args:
            prs: List of PR data dictionaries

        Returns:
            List of expert data dictionaries
        """
        author_counts: Counter = Counter()
        reviewer_counts: Counter = Counter()

        for pr in prs:
            author = pr.get("author")
            if author:
                author_counts[author] += 1

        # Combine authors and reviewers
        all_experts = author_counts + reviewer_counts

        experts = [
            {"username": username, "pr_count": count, "role": "author"}
            for username, count in all_experts.most_common(10)
        ]

        return experts

    def _find_relevant_communities(self, purpose: str) -> list[dict]:
        """Find code communities related to the agent's purpose.

        Args:
            purpose: The agent's specialization purpose

        Returns:
            List of community data dictionaries
        """
        # Use semantic search to find related code
        search_result = self.toolkit.execute(
            "semantic_search",
            query=purpose,
            limit=20,
        )

        if not search_result.success:
            return []

        results = search_result.data.get("results", [])

        # Get communities from search results
        communities_result = self.toolkit.execute(
            "get_communities",
            query=purpose,
            limit=5,
        )

        communities = []
        if communities_result.success:
            for comm in communities_result.data.get("communities", []):
                communities.append({
                    "id": comm.get("id"),
                    "name": comm.get("description", f"Community {comm.get('id')}"),
                    "size": comm.get("size", 0),
                })

        return communities

    def _collect_relevant_files(self, context: AgentBuilderContext) -> list[str]:
        """Collect all relevant files from gathered context.

        Args:
            context: The builder context with PR data

        Returns:
            List of file paths
        """
        files = set()

        for pr in context.related_prs:
            files_changed = pr.get("files_changed", [])
            if isinstance(files_changed, list):
                files.update(files_changed)

        # Also add files from review comments
        for pattern in context.review_patterns:
            path = pattern.get("path")
            if path:
                files.add(path)

        return list(files)[:50]

    def _synthesize_hints(self, context: AgentBuilderContext) -> list[str]:
        """Use LLM to synthesize hints from gathered patterns.

        Args:
            context: The builder context with patterns

        Returns:
            List of hint strings
        """
        if not context.review_patterns and not context.things_to_avoid:
            return []

        # Build data for LLM
        data = {
            "purpose": context.purpose,
            "positive_patterns": context.review_patterns[:15],
            "anti_patterns": context.things_to_avoid[:10],
            "domain_experts": context.domain_experts[:5],
        }

        prompt = SYNTHESIS_PROMPT.format(data=json.dumps(data, indent=2))

        try:
            response = self.provider.chat([
                {"role": "user", "content": prompt}
            ])

            content = response.content or ""

            # Parse JSON from response
            # Handle case where LLM wraps in markdown code block
            if "```json" in content:
                content = content.split("```json")[1].split("```")[0]
            elif "```" in content:
                content = content.split("```")[1].split("```")[0]

            hints = json.loads(content.strip())
            if isinstance(hints, list):
                return [str(h) for h in hints[:10]]
        except Exception as e:
            log.warning(f"Failed to synthesize hints: {e}")

        return []

    def _select_mcp_tools(self) -> list[str]:
        """Interactive MCP tool selection.

        Returns:
            List of selected tool names
        """
        self.console.print("\n[bold]Available MCP Tools:[/bold]")
        for i, (name, desc) in enumerate(AVAILABLE_MCP_TOOLS, 1):
            self.console.print(f"  {i}. [cyan]{name}[/cyan]: {desc}")

        self.console.print("\n[dim]Enter tool numbers separated by commas, or 'all' for all tools:[/dim]")
        selection = Prompt.ask("Select tools", default="all")

        if selection.lower() == "all":
            return [name for name, _ in AVAILABLE_MCP_TOOLS]

        selected = []
        for part in selection.split(","):
            try:
                idx = int(part.strip()) - 1
                if 0 <= idx < len(AVAILABLE_MCP_TOOLS):
                    selected.append(AVAILABLE_MCP_TOOLS[idx][0])
            except ValueError:
                continue

        return selected or [name for name, _ in AVAILABLE_MCP_TOOLS]

    def generate_agents_md(self, context: AgentBuilderContext) -> str:
        """Generate AGENTS.md content from gathered context.

        Args:
            context: The builder context

        Returns:
            Markdown string
        """
        sections = []

        # Header
        sections.append(f"# {context.name}\n\n")

        # Purpose
        sections.append("## Purpose\n\n")
        sections.append(f"{context.purpose}\n\n")

        # Knowledge Context
        sections.append("## Knowledge Context\n\n")

        if context.code_communities:
            sections.append("### Code Areas\n\n")
            for comm in context.code_communities[:5]:
                name = comm.get("name", f"Community {comm.get('id')}")
                size = comm.get("size", 0)
                sections.append(f"- **{name}** ({size} entities)\n")
            sections.append("\n")

        if context.relevant_files:
            sections.append("### Key Files\n\n")
            for f in context.relevant_files[:15]:
                sections.append(f"- `{f}`\n")
            sections.append("\n")

        # Hints
        sections.append("## Hints\n\n")
        if context.hints:
            for i, hint in enumerate(context.hints, 1):
                sections.append(f"{i}. {hint}\n")
        else:
            sections.append("_No hints synthesized - analyze more PRs for better results._\n")
        sections.append("\n")

        # Things to Avoid
        sections.append("## Things to Avoid\n\n")
        if context.things_to_avoid:
            for anti in context.things_to_avoid[:10]:
                pr = anti.get("pr", "")
                feedback = anti.get("feedback", "")[:200]
                reviewer = anti.get("reviewer", "")
                sections.append(f"- (PR #{pr}, @{reviewer}) {feedback}\n")
        else:
            sections.append("_No anti-patterns found in reviewed PRs._\n")
        sections.append("\n")

        # Related PRs
        sections.append("## Related PRs\n\n")
        for pr in context.related_prs[:10]:
            num = pr.get("number")
            title = pr.get("title", "")
            author = pr.get("author", "")
            sections.append(f"- **PR #{num}**: {title} (by @{author})\n")
        sections.append("\n")

        # Domain Experts
        sections.append("## Domain Experts\n\n")
        if context.domain_experts:
            for expert in context.domain_experts[:5]:
                username = expert.get("username")
                count = expert.get("pr_count", 0)
                sections.append(f"- @{username} ({count} PRs in this area)\n")
        else:
            sections.append("_No domain experts identified._\n")
        sections.append("\n")

        # MCP Tools
        sections.append("## MCP Tools\n\n")
        sections.append("This agent uses the following tools (see `mcp.json`):\n\n")
        for tool in context.selected_mcp_tools:
            sections.append(f"- `{tool}`\n")

        return "".join(sections)

    def generate_mcp_json(self, context: AgentBuilderContext) -> dict:
        """Generate mcp.json configuration.

        Args:
            context: The builder context

        Returns:
            MCP configuration dictionary
        """
        return {
            "mcpServers": {
                "github": {
                    "command": "github-mcp-server",
                    "args": ["stdio"],
                    "env": {
                        "GITHUB_PERSONAL_ACCESS_TOKEN": "${GITHUB_TOKEN}"
                    }
                }
            },
            "agentName": context.name,
            "purpose": context.purpose,
            "selectedTools": context.selected_mcp_tools,
            "domainExperts": [e.get("username") for e in context.domain_experts],
        }

    def save(
        self,
        context: AgentBuilderContext,
        output_dir: Optional[Path] = None,
    ) -> Path:
        """Save the generated agent files.

        Args:
            context: Built agent context
            output_dir: Optional output directory

        Returns:
            Path to the created agent directory
        """
        if output_dir is None:
            output_dir = Path.cwd() / ".emdash-rules" / "agents" / context.name

        output_dir.mkdir(parents=True, exist_ok=True)

        # Save AGENTS.md
        agents_md = self.generate_agents_md(context)
        (output_dir / "AGENTS.md").write_text(agents_md, encoding="utf-8")

        # Save mcp.json
        mcp_config = self.generate_mcp_json(context)
        (output_dir / "mcp.json").write_text(
            json.dumps(mcp_config, indent=2),
            encoding="utf-8"
        )

        return output_dir
