"""GitHub pull_request event handler.

On pull_request.opened (or .reopened):
  1. Finds tasks whose ``branch`` matches the PR's head ref; updates their
     ``pr_url``.
  2. Parses the PR title and body for issue references (e.g. ``fixes #5``,
     ``closes #10``, bare ``#42``). For each referenced number that matches a
     Peon task ID, sets the pr_url.
"""

from __future__ import annotations

import logging
import re

from peon_mcp.db import get_pool
from peon_mcp.github_webhooks.dispatcher import register_handler

logger = logging.getLogger(__name__)

# Matches GitHub closing keywords followed by an issue number, plus bare #N.
# Examples: "fixes #5", "Closes #10", "Resolves: #42", "#99"
_ISSUE_REF_RE = re.compile(
    r"(?:(?:fix(?:es|ed)?|close[sd]?|resolve[sd]?)\s*:?\s*)?#(\d+)",
    re.IGNORECASE,
)


def _extract_issue_numbers(text: str) -> list[int]:
    """Return unique issue numbers referenced in *text*, in order of appearance."""
    seen: set[int] = set()
    result: list[int] = []
    for match in _ISSUE_REF_RE.finditer(text):
        n = int(match.group(1))
        if n not in seen:
            seen.add(n)
            result.append(n)
    return result


@register_handler("pull_request")
async def handle_pull_request(delivery_id: str, payload: dict) -> None:
    """Handle pull_request webhook events.

    Reacts to ``opened`` and ``reopened`` actions.

    Branch match: for every task whose ``branch`` matches the PR's head ref,
    set ``pr_url`` (if not already set).

    Issue reference match: parse the PR title and body for ``#N`` references.
    For each referenced N that is a known task ID, set the task's ``pr_url``.
    """
    action = payload.get("action", "")
    if action not in ("opened", "reopened"):
        return

    pr = payload.get("pull_request", {})
    branch: str = pr.get("head", {}).get("ref", "")
    pr_url: str = pr.get("html_url", "")
    pr_title: str = pr.get("title", "")
    pr_body: str = pr.get("body") or ""

    if not branch:
        logger.debug("pr_handler: delivery=%s missing head.ref, skipping", delivery_id)
        return

    logger.info(
        "pr_handler: delivery=%s action=%s branch=%s",
        delivery_id,
        action,
        branch,
    )

    # Collect issue numbers from PR title and body.
    ref_numbers = _extract_issue_numbers(pr_title + " " + pr_body)

    pool = get_pool()
    async with pool.acquire() as conn:
        # --- Branch-based task lookup ---
        rows = await conn.fetch(
            "SELECT id FROM tasks WHERE branch = $1", branch
        )
        task_ids: list[int] = [row["id"] for row in rows]

        # --- Issue-reference-based lookups ---
        if ref_numbers:
            # Tasks referenced by number
            rows = await conn.fetch(
                "SELECT id FROM tasks WHERE id = ANY($1)",
                ref_numbers,
            )
            for row in rows:
                tid = row["id"]
                if tid not in task_ids:
                    task_ids.append(tid)

        if not task_ids:
            logger.debug(
                "pr_handler: delivery=%s no tasks found for branch=%s refs=%s",
                delivery_id,
                branch,
                ref_numbers,
            )
            return

        # Set pr_url on tasks that don't already have one.
        if pr_url:
            for task_id in task_ids:
                await conn.execute(
                    """UPDATE tasks
                       SET pr_url = $1, updated_at = NOW()
                       WHERE id = $2 AND pr_url = ''""",
                    pr_url, task_id,
                )

        logger.info(
            "pr_handler: delivery=%s updated %d task(s) for branch=%s refs=%s",
            delivery_id,
            len(task_ids),
            branch,
            ref_numbers,
        )
