"""GitHub webhook handler: PR review automation.

Two handlers:

1. **PR opened → conduct code review**: When a PR is opened, creates a
   review task so a peon agent reviews the code and posts feedback.

2. **Review feedback → fix task**: When a reviewer requests changes,
   creates a high-priority task so the loop addresses the feedback.
"""

from __future__ import annotations

import logging
from datetime import UTC, datetime
from typing import Any

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

logger = logging.getLogger(__name__)


async def _find_task_for_pr(
    conn,
    pr_url: str,
) -> dict[str, Any] | None:
    """Find the most recent task linked to a PR URL.

    Includes ``done`` tasks because by the time a reviewer requests changes
    the original task is typically already completed.  Only excludes
    ``cancelled`` tasks.
    """
    rows = await conn.fetch(
        """SELECT id, project_id, feature_id, branch, pr_url
           FROM tasks
           WHERE pr_url = $1
             AND status != 'cancelled'
           ORDER BY updated_at DESC
           LIMIT 1""",
        pr_url,
    )
    if rows:
        return dict(rows[0])
    return None


async def _find_project_for_repo(
    conn,
    repo_full_name: str,
) -> str | None:
    """Find a project_id whose github_url matches the repository."""
    rows = await conn.fetch(
        "SELECT id, github_url FROM projects WHERE github_url != ''"
    )
    for row in rows:
        url = (row["github_url"] or "").rstrip("/").removesuffix(".git")
        if url.endswith(repo_full_name):
            return row["id"]
    return None


async def _review_task_exists(
    conn,
    pr_url: str,
    reviewer: str,
) -> bool:
    """Check if a pending review-fix task already exists for this PR+reviewer."""
    rows = await conn.fetch(
        """SELECT 1 FROM tasks
           WHERE pr_url = $1
             AND task_type = 'review'
             AND title ILIKE $2
             AND status NOT IN ('done', 'cancelled')
           LIMIT 1""",
        pr_url, f"%{reviewer}%",
    )
    return bool(rows)


def _build_description(
    reviewer: str,
    review_body: str,
    pr_number: int,
    pr_url: str,
    repo: str,
) -> str:
    """Build the task description with review context."""
    lines = [
        f"A GitHub reviewer (**@{reviewer}**) requested changes on PR "
        f"[#{pr_number}]({pr_url}) in `{repo}`.",
        "",
        "## Review Comments",
        "",
        review_body or "*No body provided — check the PR for inline comments.*",
        "",
        "## Instructions",
        "",
        f"1. Check out the PR branch and read the review comments on {pr_url}",
        "2. Use `gh pr view` and `gh api` to fetch inline review comments",
        "3. Address each piece of feedback",
        "4. Commit and push the fixes to the PR branch",
        "5. Do NOT create a new PR — push to the existing branch",
    ]
    return "\n".join(lines)


@register_handler("pull_request_review")
async def handle_pull_request_review(delivery_id: str, payload: dict[str, Any]) -> None:
    """Handle ``pull_request_review`` events.

    Only acts on ``submitted`` action with ``changes_requested`` state.
    Creates a high-priority task for the loop to address the review feedback.
    """
    action = payload.get("action")
    if action != "submitted":
        return

    review = payload.get("review", {})
    review_state = (review.get("state") or "").lower()

    if review_state != "changes_requested":
        return

    reviewer = (review.get("user") or {}).get("login", "unknown")
    review_body = review.get("body") or ""

    pr = payload.get("pull_request", {})
    pr_number = pr.get("number")
    pr_url = pr.get("html_url", "")
    branch = (pr.get("head") or {}).get("ref", "")
    repo = (payload.get("repository") or {}).get("full_name", "")

    if not pr_number or not pr_url:
        logger.warning(
            "pr_review: delivery %s — missing PR number or URL; skipping",
            delivery_id,
        )
        return

    logger.info(
        "pr_review: delivery=%s reviewer=%s state=%s pr=#%s repo=%s",
        delivery_id, reviewer, review_state, pr_number, repo,
    )

    try:
        pool = get_pool()
        async with pool.acquire() as conn:
            # Find the original task linked to this PR (includes done tasks).
            original_task = await _find_task_for_pr(conn, pr_url)

            if original_task:
                project_id = original_task["project_id"]
                feature_id = original_task["feature_id"]
            else:
                # Fallback: find project by matching repo URL.
                project_id = await _find_project_for_repo(conn, repo) if repo else None
                feature_id = None
                if not project_id:
                    logger.info(
                        "pr_review: delivery %s — no linked task or project found "
                        "for pr_url=%s repo=%s; skipping",
                        delivery_id, pr_url, repo,
                    )
                    return
                logger.info(
                    "pr_review: delivery %s — no linked task; using project=%s from repo",
                    delivery_id, project_id,
                )

            # Don't create duplicate review tasks for the same reviewer+PR.
            if await _review_task_exists(conn, pr_url, reviewer):
                logger.info(
                    "pr_review: delivery %s — review task already exists for "
                    "%s on %s; skipping",
                    delivery_id, reviewer, pr_url,
                )
                return

            title = f"Address PR review feedback from @{reviewer} on #{pr_number}"
            description = _build_description(
                reviewer, review_body, pr_number, pr_url, repo,
            )
            now = datetime.now(UTC)

            new_task_id = await conn.fetchval(
                """INSERT INTO tasks
                       (project_id, feature_id, title, description, priority,
                        status, branch, pr_url, task_type, created_at, updated_at)
                   VALUES ($1, $2, $3, $4, 'high', 'todo', $5, $6, 'review', $7, $8)
                   RETURNING id""",
                project_id,
                feature_id,
                title,
                description,
                branch,
                pr_url,
                now,
                now,
            )

            logger.info(
                "pr_review: created task #%d for project=%s — %s",
                new_task_id, project_id, title,
            )

    except Exception:
        logger.exception(
            "pr_review: delivery %s — error creating review task for %s",
            delivery_id, pr_url,
        )


# ---------------------------------------------------------------------------
# PR opened → conduct code review
# ---------------------------------------------------------------------------


def _build_code_review_description(
    pr_number: int,
    pr_url: str,
    pr_title: str,
    pr_body: str,
    repo: str,
    branch: str,
    additions: int,
    deletions: int,
    changed_files: int,
) -> str:
    """Build the task description for a peon-conducted code review."""
    lines = [
        f"Conduct a code review of PR [#{pr_number}]({pr_url}) in `{repo}`.",
        "",
        f"**Title**: {pr_title}",
        f"**Branch**: `{branch}`",
        f"**Changes**: +{additions} / -{deletions} across {changed_files} files",
        "",
    ]
    if pr_body:
        lines += [
            "## PR Description",
            "",
            pr_body,
            "",
        ]
    lines += [
        "## Instructions",
        "",
        f"1. Fetch the PR diff: `gh pr diff {pr_number} --repo {repo}`",
        f"2. View PR details: `gh pr view {pr_number} --repo {repo}`",
        "3. Review the code for:",
        "   - Bugs and logic errors",
        "   - Security vulnerabilities",
        "   - Performance issues",
        "   - Code style and maintainability",
        "   - Missing tests or error handling",
        "",
        "## Submitting Your Review",
        "",
        "You MUST submit a formal GitHub review using `--comment` or `--request-changes`. "
        "Do NOT use `--approve` — the GitHub App cannot approve its own PRs. "
        "Humans are the final approvers.",
        "",
        "**When no issues found** (`--comment`): The code looks correct, safe, and ready to merge. "
        "Submit a COMMENT review summarising your findings.",
        "",
        "**When issues found** (`--request-changes`): There are bugs, security issues, "
        "missing error handling, or logic errors that must be fixed before merging.",
        "",
        f"No issues: `gh pr review {pr_number} --repo {repo} --comment --body '<summary>'`",
        f"Request changes: `gh pr review {pr_number} --repo {repo} --request-changes --body '<summary>'`",
        "",
        "For inline comments, use the GitHub API. IMPORTANT: use event='REQUEST_CHANGES' when "
        "you find bugs so that fix tasks are auto-created. Only use event='COMMENT' when the "
        "code is clean and you're just adding observations.",
        "",
        f"   Issues found (triggers fix task): `gh api repos/{repo}/pulls/{pr_number}/reviews "
        "--method POST -f body='summary' -f event='REQUEST_CHANGES' "
        "-f 'comments[][path]=file.py' -f 'comments[][position]=10' "
        "-f 'comments[][body]=issue description'`",
        "",
        f"   No issues: `gh api repos/{repo}/pulls/{pr_number}/reviews "
        "--method POST -f body='summary' -f event='COMMENT' "
        "-f 'comments[][path]=file.py' -f 'comments[][position]=10' "
        "-f 'comments[][body]=observation'`",
    ]
    return "\n".join(lines)


@register_handler("pull_request")
async def handle_pull_request_opened_review(
    delivery_id: str, payload: dict[str, Any]
) -> None:
    """Handle ``pull_request.opened`` events: create a code review task.

    When a PR is opened, creates a review task so a peon agent can review
    the code and post feedback on the PR.
    """
    action = payload.get("action")
    if action != "opened":
        return

    pr = payload.get("pull_request", {})
    pr_number = pr.get("number")
    pr_url = pr.get("html_url", "")
    pr_title = pr.get("title", "")
    pr_body = pr.get("body") or ""
    branch = (pr.get("head") or {}).get("ref", "")
    additions = pr.get("additions", 0)
    deletions = pr.get("deletions", 0)
    changed_files = pr.get("changed_files", 0)
    repo = (payload.get("repository") or {}).get("full_name", "")

    if not pr_number or not pr_url or not repo:
        logger.warning(
            "pr_review: delivery %s — missing PR info for code review; skipping",
            delivery_id,
        )
        return

    logger.info(
        "pr_review: delivery=%s creating code review task for PR #%s on %s",
        delivery_id, pr_number, repo,
    )

    try:
        pool = get_pool()
        async with pool.acquire() as conn:
            # Find project by matching github_url to repo.
            project_id = await _find_project_for_repo(conn, repo)
            if not project_id:
                logger.info(
                    "pr_review: delivery %s — no project found for repo=%s; "
                    "skipping code review task",
                    delivery_id, repo,
                )
                return

            # Find the feature_id from the task linked to this branch/PR.
            original_task = await _find_task_for_pr(conn, pr_url)
            feature_id = original_task["feature_id"] if original_task else None

            # Don't create duplicate review tasks for the same PR.
            existing = await conn.fetch(
                """SELECT 1 FROM tasks
                   WHERE pr_url = $1
                     AND task_type = 'review'
                     AND title ILIKE '%Code review%'
                     AND status NOT IN ('done', 'cancelled')
                   LIMIT 1""",
                pr_url,
            )
            if existing:
                logger.info(
                    "pr_review: delivery %s — code review task already exists "
                    "for %s; skipping",
                    delivery_id, pr_url,
                )
                return

            title = f"Code review PR #{pr_number}: {pr_title}"
            # Truncate title to reasonable length
            if len(title) > 200:
                title = title[:197] + "..."

            description = _build_code_review_description(
                pr_number, pr_url, pr_title, pr_body, repo,
                branch, additions, deletions, changed_files,
            )
            now = datetime.now(UTC)

            new_task_id = await conn.fetchval(
                """INSERT INTO tasks
                       (project_id, feature_id, title, description, priority,
                        status, branch, pr_url, task_type, created_at, updated_at)
                   VALUES ($1, $2, $3, $4, 'medium', 'todo', $5, $6, 'review', $7, $8)
                   RETURNING id""",
                project_id,
                feature_id,
                title,
                description,
                branch,
                pr_url,
                now,
                now,
            )

            logger.info(
                "pr_review: created code review task #%d for project=%s — %s",
                new_task_id, project_id, title,
            )

    except Exception:
        logger.exception(
            "pr_review: delivery %s — error creating code review task for %s",
            delivery_id, pr_url,
        )
