"""Merge conflict webhook handler.

Listens for ``pull_request`` events where the PR's ``mergeable_state`` is
``dirty`` (GitHub's term for merge conflicts).  When detected, creates a
high-priority ``fix`` task so a peon-loop agent can resolve the conflicts.

GitHub sends ``pull_request.synchronize`` when a PR branch is updated and
``pull_request.opened``/``reopened`` on open/reopen.  The ``mergeable`` field
may be ``null`` initially (GitHub computes it asynchronously), but by the time
the ``synchronize`` event fires after a base branch update, it's usually set.
"""

from __future__ import annotations

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

from peon_mcp.db import get_pool
from peon_mcp.grukk.discord.embeds import COLOR_RED, _embed, _field
from peon_mcp.grukk.discord.sender import send_to_channel
from peon_mcp.github_webhooks.dispatcher import register_handler

logger = logging.getLogger(__name__)

# Discord subscription event key for merge conflict notifications.
SUBSCRIPTION_EVENT = "pr.conflict"


# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------


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 _find_task_for_pr(
    conn,
    pr_url: str,
) -> dict[str, Any] | None:
    """Find the most recent non-terminal task linked to a PR URL."""
    rows = await conn.fetch(
        """SELECT id, project_id, feature_id, branch, pr_url
           FROM tasks
           WHERE pr_url = $1
             AND status NOT IN ('done', 'cancelled')
           ORDER BY updated_at DESC
           LIMIT 1""",
        pr_url,
    )
    if rows:
        return dict(rows[0])
    return None


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


def _build_conflict_description(
    pr_number: int,
    pr_url: str,
    pr_title: str,
    repo: str,
    branch: str,
    base_branch: str,
) -> str:
    """Build the task description for a merge conflict resolution task."""
    lines = [
        f"PR [#{pr_number}]({pr_url}) in `{repo}` has merge conflicts that "
        "need to be resolved.",
        "",
        f"**PR title**: {pr_title}",
        f"**Branch**: `{branch}`",
        f"**Base branch**: `{base_branch}`",
        "",
        "## Instructions",
        "",
        f"1. Check out the PR branch: `git checkout {branch}`",
        f"2. Merge the base branch: `git merge origin/{base_branch}`",
        "3. Resolve all merge conflicts carefully:",
        "   - Read both sides of each conflict to understand intent",
        "   - Preserve functionality from both branches where possible",
        "   - If unsure, prefer the PR branch changes (they're the newer work)",
        "4. Run the test suite to make sure the resolution doesn't break anything",
        "5. Commit the merge and push to the existing branch",
        "6. Do NOT create a new PR — push to the existing branch",
        f"7. Verify the PR at {pr_url} shows as mergeable after your push",
    ]
    return "\n".join(lines)


# ---------------------------------------------------------------------------
# Discord helpers
# ---------------------------------------------------------------------------


def _build_conflict_embed(
    pr_number: int,
    pr_url: str,
    pr_title: str,
    repo: str,
    branch: str,
    base_branch: str,
    fix_task_id: int,
) -> dict:
    """Build a Discord embed for a newly-detected merge conflict."""
    description = (
        f"**[#{pr_number}: {pr_title}]({pr_url})**\n"
        f"Branch `{branch}` has conflicts with `{base_branch}`."
    )
    return _embed(
        title="Merge Conflict Detected",
        description=description,
        color=COLOR_RED,
        project_id=repo or "github",
        fields=[
            _field("Repository", f"`{repo}`", inline=True),
            _field("Branch", f"`{branch}` → `{base_branch}`", inline=True),
            _field("Fix task", f"#{fix_task_id}", inline=True),
        ],
    )


async def _notify_discord(conn, embed: dict) -> None:
    """Send conflict embed to all channels subscribed to ``pr.conflict``."""
    rows = await conn.fetch(
        """SELECT dc.id AS config_id, dc.bot_token,
                  dcm.channel_id, dcm.subscribed_events
           FROM discord_configs dc
           JOIN discord_channel_mappings dcm ON dcm.discord_config_id = dc.id
           WHERE dc.enabled = true AND dcm.enabled = true
             AND dc.bot_token != ''""",
    )

    sent = 0
    for row in rows:
        try:
            subscribed: list = json.loads(row["subscribed_events"] or "[]")
        except (json.JSONDecodeError, TypeError):
            subscribed = []

        if SUBSCRIPTION_EVENT not in subscribed:
            continue

        gateway = {"bot_token": row["bot_token"]}
        ok = await send_to_channel(gateway, row["channel_id"], embed)
        if ok:
            sent += 1
        else:
            logger.warning(
                "merge_conflict: failed to notify Discord channel %s",
                row["channel_id"],
            )

    if sent:
        logger.info("merge_conflict: notified %d Discord channel(s)", sent)


# ---------------------------------------------------------------------------
# Handler
# ---------------------------------------------------------------------------


@register_handler("pull_request")
async def handle_merge_conflict(
    delivery_id: str, payload: dict[str, Any]
) -> None:
    """Handle ``pull_request`` events where the PR has merge conflicts.

    Acts on ``synchronize``, ``opened``, and ``reopened`` actions where
    ``mergeable_state == "dirty"`` or ``mergeable == false``.
    """
    action = payload.get("action")
    if action not in ("synchronize", "opened", "reopened"):
        return

    pr = payload.get("pull_request", {})

    # GitHub uses mergeable_state "dirty" for conflicts.
    # mergeable can be null (still computing), true, or false.
    mergeable = pr.get("mergeable")
    mergeable_state = pr.get("mergeable_state", "")

    # Only act when we're sure there are conflicts.
    has_conflicts = mergeable is False or mergeable_state == "dirty"
    if not has_conflicts:
        return

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

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

    logger.info(
        "merge_conflict: delivery=%s PR #%s on %s has conflicts "
        "(mergeable=%s, mergeable_state=%s)",
        delivery_id, pr_number, repo, mergeable, mergeable_state,
    )

    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(
                    "merge_conflict: delivery %s — no project found for repo=%s; "
                    "skipping task creation",
                    delivery_id, repo,
                )
                return

            # Find the original task linked to this 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 conflict tasks for the same PR.
            if await _conflict_task_exists(conn, pr_url):
                logger.info(
                    "merge_conflict: delivery %s — conflict task already exists "
                    "for %s; skipping",
                    delivery_id, pr_url,
                )
                return

            title = f"Resolve merge conflicts on PR #{pr_number}"
            if len(title) > 200:
                title = title[:197] + "..."

            description = _build_conflict_description(
                pr_number, pr_url, pr_title, repo, branch, base_branch,
            )
            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, 'fix', $7, $8)
                   RETURNING id""",
                project_id,
                feature_id,
                title,
                description,
                branch,
                pr_url,
                now,
                now,
            )

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

            # Best-effort Discord notification — never blocks task creation.
            try:
                embed = _build_conflict_embed(
                    pr_number=pr_number,
                    pr_url=pr_url,
                    pr_title=pr_title,
                    repo=repo,
                    branch=branch,
                    base_branch=base_branch,
                    fix_task_id=new_task_id,
                )
                await _notify_discord(conn, embed)
            except Exception:
                logger.debug(
                    "merge_conflict: Discord notification failed (non-fatal)",
                    exc_info=True,
                )

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