"""Handle pull_request events — analyze PR against specs and post context."""

from __future__ import annotations

import logging

from specwright import analytics
from specwright.settings import Settings

from ...agent.analyzer import analyze_pr, format_analysis_comment
from ...agent.client import DEFAULT_AGENT_CONFIG
from ...agent.prompts import ContextDoc, PRAnalysisContext, PRFile, RepoSpec
from ...config.parse import DEFAULT_CONFIG, parse_specwright_yaml
from ..spec_utils import (
    CONFIG_ONLY_RE,
    filter_spec_files,
    load_repo_config,
    load_repo_docs,
    load_repo_specs,
)

logger = logging.getLogger(__name__)
_settings = Settings()


def _get_search_deps() -> tuple | None:
    """Return (search_index, embed_client) from app.state, or None if unavailable."""
    try:
        from specwright.main import app

        search_index = getattr(app.state, "search_index", None)
        if search_index is None:
            return None
        embed_client = getattr(app.state, "embed_client", None)
        return (search_index, embed_client)
    except Exception:
        return None


def _get_agent_store():
    """Return AgentStore from app.state, or None if unavailable."""
    try:
        from specwright.main import app

        return getattr(app.state, "agent_store", None)
    except Exception:
        return None


def _build_search_query(pr: dict, raw_files: list[dict], max_chars: int = 500) -> str:
    """Build a search query from PR title + body + filenames."""
    parts: list[str] = [pr["title"]]
    body = (pr.get("body") or "")[:200]
    if body:
        parts.append(body)
    for f in raw_files:
        filename = f["filename"]
        if not CONFIG_ONLY_RE.match(filename):
            parts.append(filename)
    query = " ".join(parts)
    return query[:max_chars]


async def _retrieve_context_docs(
    search_index,
    embed_client,
    query: str,
    repo: str,
    spec_paths: set[str],
    max_chars: int = 8000,
) -> list[ContextDoc]:
    """Retrieve relevant context docs from the search index."""
    query_embedding = None
    if embed_client and embed_client.is_available:
        try:
            query_embedding = embed_client.embed_query(query)
        except Exception:
            logger.debug("Failed to embed search query, using text-only search")

    try:
        results = await search_index.hybrid_search(
            query_embedding=query_embedding,
            query_text=query,
            repo=repo,
            limit=30,
        )
    except Exception:
        logger.debug("Search index query failed for context docs")
        return []

    seen: set[tuple[str, str]] = set()
    docs: list[ContextDoc] = []
    chars_used = 0

    for r in results:
        # Skip results that are already loaded as full specs
        if r.path in spec_paths:
            continue

        key = (r.path, r.heading)
        if key in seen:
            continue
        seen.add(key)

        body_len = len(r.body)
        if chars_used + body_len > max_chars:
            continue

        docs.append(
            ContextDoc(
                path=r.path,
                doc_title=r.doc_title,
                heading=r.heading,
                body=r.body,
                score=r.rrf_score,
            )
        )
        chars_used += body_len

    return docs


async def _load_context_docs_fallback(
    client,
    owner: str,
    repo: str,
    spec_paths: set[str],
    ref: str | None = None,
    max_chars: int = 8000,
) -> list[ContextDoc]:
    """Fallback: load docs directly from GitHub using doc_paths from SPECWRIGHT.yaml."""
    # Load config to get doc_paths
    doc_paths = None
    try:
        content, _sha = await client.get_file_content(owner, repo, "SPECWRIGHT.yaml", ref=ref)
        result = parse_specwright_yaml(content)
        if result.config:
            doc_paths = result.config.specs.doc_paths
    except Exception:
        pass

    if not doc_paths:
        doc_paths = DEFAULT_CONFIG.specs.doc_paths

    try:
        all_docs = await load_repo_docs(client, owner, repo, patterns=doc_paths, ref=ref)
    except Exception:
        logger.debug("Failed to load docs via fallback for %s/%s", owner, repo)
        return []

    docs: list[ContextDoc] = []
    chars_used = 0

    for doc_data in all_docs:
        file_path = doc_data["file_path"]
        # Skip files already loaded as specs
        if file_path in spec_paths:
            continue

        document = doc_data["document"]
        for section in getattr(document, "sections", []):
            body = section.content[:2000] if hasattr(section, "content") else ""
            if not body:
                continue

            body_len = len(body)
            if chars_used + body_len > max_chars:
                break

            docs.append(
                ContextDoc(
                    path=file_path,
                    doc_title=getattr(document.frontmatter, "title", file_path),
                    heading=section.title,
                    body=body,
                )
            )
            chars_used += body_len

        if chars_used >= max_chars:
            break

    return docs


async def on_pull_request(client, payload: dict) -> None:
    """Handle a GitHub pull_request event.

    Args:
        client: GitHubClient instance.
        payload: The webhook payload.
    """
    pr = payload["pull_request"]
    owner = payload["repository"]["owner"]["login"]
    repo = payload["repository"]["name"]
    pr_number = pr["number"]
    tag = f"[specwright] {owner}/{repo}#{pr_number}"

    try:
        raw_files = await client.list_pull_files(owner, repo, pr_number)

        # Early return if only config/CI files
        has_non_config = any(not CONFIG_ONLY_RE.match(f["filename"]) for f in raw_files)
        if not has_non_config:
            return

        # Load repo config to get configurable doc_paths
        config = await load_repo_config(client, owner, repo, ref=pr["base"]["ref"])
        doc_paths = config.specs.doc_paths

        # Detect spec file changes and add label
        spec_changed_files = filter_spec_files(
            [f["filename"] for f in raw_files], patterns=doc_paths
        )
        if spec_changed_files:
            try:
                await client.ensure_label(
                    owner,
                    repo,
                    "spec-review",
                    color="7057ff",
                    description="PR modifies spec files",
                )
                # Add label to the PR (PRs are issues in GitHub API)
                existing_labels = [lbl["name"] for lbl in pr.get("labels", [])]
                if "spec-review" not in existing_labels:
                    await client.update_issue(
                        owner, repo, pr_number, labels=[*existing_labels, "spec-review"]
                    )
            except Exception:
                logger.warning("%s — failed to add spec-review label", tag, exc_info=True)

        # Load all repo specs
        specs_data = await load_repo_specs(
            client, owner, repo, ref=pr["base"]["ref"], patterns=doc_paths
        )

        if not specs_data:
            spec_files = filter_spec_files([f["filename"] for f in raw_files], patterns=doc_paths)
            if not spec_files:
                return

            file_list = "\n".join(f"- `{f}`" for f in spec_files)
            await client.upsert_bot_comment(
                owner,
                repo,
                pr_number,
                f"<!-- specwright-bot -->\n## Specwright\n\nThis PR modifies the following spec files:\n\n{file_list}\n\n_No other specs found for cross-reference._",
            )
            return

        # Retrieve context docs from search index or fallback
        spec_paths = {s["file_path"] for s in specs_data}
        context_docs: list[ContextDoc] = []
        search_deps = _get_search_deps()
        if search_deps:
            search_index, embed_client = search_deps
            query = _build_search_query(pr, raw_files)
            context_docs = await _retrieve_context_docs(
                search_index,
                embed_client,
                query,
                f"{owner}/{repo}",
                spec_paths,
            )
        else:
            context_docs = await _load_context_docs_fallback(
                client,
                owner,
                repo,
                spec_paths,
                ref=pr["base"]["ref"],
            )

        # Post a "processing" comment
        spec_list = ", ".join(f"`{s['file_path']}`" for s in specs_data)
        ctx_note = f" + {len(context_docs)} context doc(s)" if context_docs else ""
        spec_changes_note = ""
        if spec_changed_files:
            file_list_md = "\n".join(f"- `{f}`" for f in spec_changed_files)
            spec_changes_note = f"\n\n### Spec Changes\n\nThis PR modifies the following spec files:\n\n{file_list_md}\n"
        await client.upsert_bot_comment(
            owner,
            repo,
            pr_number,
            f"<!-- specwright-bot -->\n## Specwright\n\n_Analyzing {len(raw_files)} file(s) against {len(specs_data)} spec(s) ({spec_list}){ctx_note}..._"
            + spec_changes_note,
        )

        # Build analysis context
        files = [
            PRFile(
                filename=f["filename"],
                status=f.get("status", "modified"),
                patch=f.get("patch"),
                additions=f.get("additions", 0),
                deletions=f.get("deletions", 0),
            )
            for f in raw_files
        ]

        repo_specs = [
            RepoSpec(file_path=s["file_path"], document=s["document"]) for s in specs_data
        ]

        context = PRAnalysisContext(
            pr=PRAnalysisContext.PRInfo(
                number=pr_number,
                title=pr["title"],
                body=pr.get("body"),
                author=pr["user"]["login"],
                base_branch=pr["base"]["ref"],
                head_branch=pr["head"]["ref"],
                url=pr["html_url"],
            ),
            files=files,
            specs=repo_specs,
            context_docs=context_docs,
        )

        result = analyze_pr(context)

        if result:
            comment = format_analysis_comment(
                result,
                model=DEFAULT_AGENT_CONFIG.model,
                base_url=_settings.platform_url,
                owner=owner,
                repo=repo,
                doc_patterns=doc_paths,
            )
            await client.upsert_bot_comment(owner, repo, pr_number, comment)
            logger.info(
                "%s — analysis posted (%din/%dout)",
                tag,
                result.tokens_used.input,
                result.tokens_used.output,
            )

            analytics.track(
                "pr_analyzed",
                properties={
                    "repo": f"{owner}/{repo}",
                    "pr_number": pr_number,
                    "spec_count": len(specs_data),
                    "realization_count": len(result.realizations),
                    "discrepancy_count": len(result.discrepancies),
                    "tokens_in": result.tokens_used.input,
                    "tokens_out": result.tokens_used.output,
                },
                groups={"organization": owner},
            )

            # Store realizations and log event (best-effort)
            if result.realizations:
                agent_store = _get_agent_store()
                if agent_store is not None:
                    full_repo = f"{owner}/{repo}"
                    try:
                        for r in result.realizations:
                            await agent_store.upsert_realization(
                                full_repo,
                                r.spec_file,
                                r.section_id,
                                r.ac_text,
                                status=r.status.value,
                                pr_number=pr_number,
                                pr_url=pr["html_url"],
                                evidence_files=r.evidence_files,
                            )
                        await agent_store.log_event(
                            full_repo,
                            "pr_comment",
                            pr_number=pr_number,
                            actor="specwright[bot]",
                            detail={
                                "realizations": len(result.realizations),
                                "discrepancies": len(result.discrepancies),
                            },
                        )
                    except Exception:
                        logger.warning("%s — failed to store realizations", tag, exc_info=True)
        else:
            spec_list_md = "\n".join(f"- `{s['file_path']}`" for s in specs_data)
            await client.upsert_bot_comment(
                owner,
                repo,
                pr_number,
                f"<!-- specwright-bot -->\n## Specwright\n\nFound {len(specs_data)} spec(s) in this repo:\n\n{spec_list_md}\n\n_Agent analysis unavailable — set ANTHROPIC_API_KEY to enable AI-powered spec analysis._",
            )
            logger.info("%s — agent unavailable, posted fallback", tag)

    except Exception as exc:
        err_msg = f"{type(exc).__name__}: {exc}"
        logger.error("%s — error: %s", tag, err_msg)

        try:
            await client.create_comment(
                owner,
                repo,
                pr_number,
                f"{client.BOT_MARKER}\n<!-- specwright-bot -->\n## Specwright\n\n_Agent analysis encountered an error. Will retry on next update._\n\n<sub>Error: {err_msg}</sub>",
            )
        except Exception:
            logger.error("%s — failed to post error comment", tag)
