"""Vassal Protocol — Gitea backlog triage.

Fetches open issues from Gitea, scores each one for priority and agent
suitability, and returns a ranked list ready for dispatch.

Complexity scoring heuristics
------------------------------
  high_complexity_keywords → route to Claude (architecture, refactor, review)
  research_keywords        → route to Kimi (survey, analysis, benchmark)
  routine_keywords         → route to Timmy/self (docs, chore, config)
  otherwise                → Timmy self-handles

Priority scoring
----------------
  URGENT label          → 100
  HIGH / critical       → 75
  NORMAL (default)      → 50
  LOW / chore           → 25
  Already assigned      → deprioritized (subtract 20)
"""

from __future__ import annotations

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

logger = logging.getLogger(__name__)

# ---------------------------------------------------------------------------
# Constants
# ---------------------------------------------------------------------------

# Labels that hint at complexity level / agent suitability
_HIGH_COMPLEXITY = frozenset(
    {
        "architecture",
        "refactor",
        "code review",
        "security",
        "performance",
        "breaking change",
        "design",
        "complex",
    }
)

_RESEARCH_KEYWORDS = frozenset(
    {
        "research",
        "survey",
        "analysis",
        "benchmark",
        "comparative",
        "investigation",
        "deep dive",
        "review",
    }
)

_ROUTINE_KEYWORDS = frozenset(
    {
        "docs",
        "documentation",
        "chore",
        "config",
        "typo",
        "rename",
        "cleanup",
        "trivial",
        "style",
    }
)

_PRIORITY_LABEL_SCORES: dict[str, int] = {
    "urgent": 100,
    "critical": 90,
    "high": 75,
    "normal": 50,
    "low": 25,
    "chore": 20,
}


# ---------------------------------------------------------------------------
# Data models
# ---------------------------------------------------------------------------


class AgentTarget(StrEnum):
    """Which agent should handle this issue."""

    TIMMY = "timmy"  # Timmy handles locally (self)
    CLAUDE = "claude"  # Dispatch to Claude Code
    KIMI = "kimi"  # Dispatch to Kimi Code


@dataclass
class TriagedIssue:
    """A Gitea issue enriched with triage metadata."""

    number: int
    title: str
    body: str
    labels: list[str] = field(default_factory=list)
    assignees: list[str] = field(default_factory=list)
    priority_score: int = 50
    agent_target: AgentTarget = AgentTarget.TIMMY
    rationale: str = ""
    url: str = ""
    raw: dict = field(default_factory=dict)


# ---------------------------------------------------------------------------
# Scoring helpers
# ---------------------------------------------------------------------------


def _extract_labels(issue: dict[str, Any]) -> list[str]:
    """Return normalised label names from a raw Gitea issue dict."""
    return [lbl.get("name", "").lower() for lbl in issue.get("labels", [])]


def _score_priority(labels: list[str], assignees: list[str]) -> int:
    score = _PRIORITY_LABEL_SCORES.get("normal", 50)
    for lbl in labels:
        for key, val in _PRIORITY_LABEL_SCORES.items():
            if key in lbl:
                score = max(score, val)
    if assignees:
        score -= 20  # already assigned — lower urgency for fresh dispatch
    return max(0, score)


def _choose_agent(title: str, body: str, labels: list[str]) -> tuple[AgentTarget, str]:
    """Heuristic: pick the best agent and return (target, rationale)."""
    combined = f"{title} {body} {' '.join(labels)}".lower()

    if any(kw in combined for kw in _HIGH_COMPLEXITY):
        return AgentTarget.CLAUDE, "high-complexity keywords detected"

    if any(kw in combined for kw in _RESEARCH_KEYWORDS):
        return AgentTarget.KIMI, "research keywords detected"

    if any(kw in combined for kw in _ROUTINE_KEYWORDS):
        return AgentTarget.TIMMY, "routine task — Timmy self-handles"

    return AgentTarget.TIMMY, "no specific routing signal — Timmy self-handles"


# ---------------------------------------------------------------------------
# Triage
# ---------------------------------------------------------------------------


def triage_issues(raw_issues: list[dict[str, Any]]) -> list[TriagedIssue]:
    """Score and route a list of raw Gitea issue dicts.

    Returns a list sorted by priority_score descending (highest first).

    Args:
        raw_issues: List of issue objects from the Gitea API.

    Returns:
        Sorted list of TriagedIssue with routing decisions.
    """
    results: list[TriagedIssue] = []

    for issue in raw_issues:
        number = issue.get("number", 0)
        title = issue.get("title", "")
        body = issue.get("body") or ""
        labels = _extract_labels(issue)
        assignees = [a.get("login", "") for a in issue.get("assignees") or []]
        url = issue.get("html_url", "")

        priority = _score_priority(labels, assignees)
        agent, rationale = _choose_agent(title, body, labels)

        results.append(
            TriagedIssue(
                number=number,
                title=title,
                body=body,
                labels=labels,
                assignees=assignees,
                priority_score=priority,
                agent_target=agent,
                rationale=rationale,
                url=url,
                raw=issue,
            )
        )

    results.sort(key=lambda i: i.priority_score, reverse=True)
    logger.debug(
        "Triage complete: %d issues → %d Claude, %d Kimi, %d Timmy",
        len(results),
        sum(1 for i in results if i.agent_target == AgentTarget.CLAUDE),
        sum(1 for i in results if i.agent_target == AgentTarget.KIMI),
        sum(1 for i in results if i.agent_target == AgentTarget.TIMMY),
    )
    return results


# ---------------------------------------------------------------------------
# Gitea fetch (async, gracefully degrading)
# ---------------------------------------------------------------------------


async def fetch_open_issues(
    limit: int = 50,
    exclude_labels: list[str] | None = None,
) -> list[dict[str, Any]]:
    """Fetch open issues from the configured Gitea repo.

    Args:
        limit: Maximum number of issues to return.
        exclude_labels: Labels whose issues should be skipped
            (e.g. ``["kimi-ready", "wip"]``).

    Returns:
        List of raw issue dicts from the Gitea API,
        or empty list if Gitea is unavailable.
    """
    try:
        import httpx

        from config import settings
    except ImportError as exc:
        logger.warning("fetch_open_issues: missing dependency — %s", exc)
        return []

    if not settings.gitea_enabled or not settings.gitea_token:
        logger.info("fetch_open_issues: Gitea disabled or no token")
        return []

    exclude = set(lbl.lower() for lbl in (exclude_labels or []))
    base_url = f"{settings.gitea_url}/api/v1"
    repo = settings.gitea_repo
    headers = {"Authorization": f"token {settings.gitea_token}"}
    params = {"state": "open", "limit": min(limit, 50), "page": 1}

    try:
        async with httpx.AsyncClient(timeout=15) as client:
            resp = await client.get(
                f"{base_url}/repos/{repo}/issues",
                headers=headers,
                params=params,
            )
        if resp.status_code != 200:
            logger.warning("fetch_open_issues: Gitea returned %s", resp.status_code)
            return []

        issues = resp.json()

        # Filter out pull requests and excluded labels
        filtered = []
        for issue in issues:
            if issue.get("pull_request"):
                continue  # skip PRs
            labels = _extract_labels(issue)
            if exclude and any(lbl in exclude for lbl in labels):
                continue
            filtered.append(issue)

        logger.info(
            "fetch_open_issues: fetched %d/%d issues (after filtering)",
            len(filtered),
            len(issues),
        )
        return filtered

    except Exception as exc:
        logger.warning("fetch_open_issues: Gitea request failed — %s", exc)
        return []
