"""navig.selfheal.pr_builder — Draft and submit a GitHub Pull Request.

Opens a PR from ``{user}/navig:{branch}`` → ``navig-run/core:main`` via the
GitHub REST API.  Authentication uses the PAT stored under vault provider
``github_contribute`` (env ``NAVIG_GITHUB_TOKEN``).

Every submission requires explicit prior user approval — this module never
auto-submits.  The calling layer (CLI command or Telegram flow) is solely
responsible for obtaining that approval before calling :func:`submit_pr`.
"""

from __future__ import annotations

import os
from datetime import datetime, timezone
from typing import Any

from loguru import logger

from navig.selfheal.git_manager import (
    UPSTREAM_REPO,
    _github_request,
    get_github_username,
)
from navig.selfheal.scanner import ScanFinding

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

_SEVERITY_EMOJI: dict[str, str] = {
    "critical": "🔴",
    "high": "🟠",
    "medium": "🟡",
    "low": "🔵",
}


# ---------------------------------------------------------------------------
# PR body formatting
# ---------------------------------------------------------------------------


def _finding_summary(findings: list[ScanFinding]) -> str:
    """Summarise findings as a bullet list grouped by severity.

    Args:
        findings: All approved scan findings.

    Returns:
        Markdown-formatted summary string.
    """
    by_sev: dict[str, list[ScanFinding]] = {}
    for f in findings:
        by_sev.setdefault(f.severity, []).append(f)

    lines: list[str] = []
    for sev in ("critical", "high", "medium", "low"):
        group = by_sev.get(sev, [])
        if not group:
            continue
        emoji = _SEVERITY_EMOJI.get(sev, "•")
        lines.append(f"**{emoji} {sev.capitalize()}** ({len(group)} issue(s))")
        for f in group:
            lines.append(f"  - `{f.file}:{f.line}` — {f.description}")
    return "\n".join(lines) or "No issues found."


def _fix_bullets(findings: list[ScanFinding]) -> str:
    """Generate a bullet list of file → fix description pairs.

    Args:
        findings: Approved scan findings.

    Returns:
        Markdown bullet list string.
    """
    lines = [f"- `{f.file}:{f.line}` → {f.suggested_fix[:100]}" for f in findings]
    return "\n".join(lines) or "- No fixes applied."


def _confidence_table(findings: list[ScanFinding]) -> str:
    """Build a Markdown table of per-finding confidence scores.

    Args:
        findings: Approved scan findings.

    Returns:
        Markdown table string.
    """
    rows = [
        f"| `{f.file}:{f.line}` | {f.description[:60]} | {f.confidence:.2f} |" for f in findings
    ]
    return "\n".join(rows) or "| — | — | — |"


def _build_pr_body(
    findings: list[ScanFinding],
    alias: str,
    version: str,
    branch: str,
) -> str:
    """Render the Pull Request body in the required format.

    Args:
        findings: Approved findings included in this PR.
        alias: Contributor alias set by the user during onboarding.
        version: NAVIG version string (e.g. ``"2.4.16"``).
        branch: Branch name (e.g. ``"navig-selfheal/20260315-a3f8c2d1"``).

    Returns:
        Full PR body string.
    """
    scan_date = datetime.now(timezone.utc).strftime("%Y-%m-%d %H:%M UTC")
    contributor = alias.strip() or "Anonymous"

    return f"""\
🔧 Auto-Contribution by NAVIG Self-Heal Engine
Contributor: {contributor} (via NAVIG v{version} Self-Heal)
Scan Date: {scan_date}

🧠 What Was Found
{_finding_summary(findings)}

✅ What Was Fixed
{_fix_bullets(findings)}

📊 Confidence Scores
| File | Fix | Confidence |
|------|-----|------------|
{_confidence_table(findings)}

🙋 About This Contribution
Reviewed and approved by the user before submission.
No core model modified. Peripheral code and integrations only.

---
*Branch: `{branch}` · Generated by [NAVIG Self-Heal](https://github.com/{UPSTREAM_REPO})*
"""


# ---------------------------------------------------------------------------
# Token resolution
# ---------------------------------------------------------------------------


def _resolve_token(config: dict[str, Any]) -> str:
    """Resolve the GitHub contribution PAT.

    Resolution order:
    1. Vault provider ``github_contribute``.
    2. Environment variable named by ``config["github_token_env"]``
       (default: ``NAVIG_GITHUB_TOKEN``).

    Args:
        config: ``contribute`` config dict from ``navig.yaml``.

    Returns:
        PAT string.

    Raises:
        ValueError: When no token can be found.
    """
    # 1. Try vault (deferred import so startup stays fast).
    try:
        from navig.vault import get_vault  # noqa: PLC0415

        vault = get_vault()
        token = vault.get_api_key("github_contribute")
        if token:
            return token
    except Exception as exc:  # noqa: BLE001
        logger.debug("Vault lookup for github_contribute failed: {}", exc)

    # 2. Fallback to environment variable.
    env_var: str = config.get("github_token_env", "NAVIG_GITHUB_TOKEN")
    token = os.environ.get(env_var, "").strip()
    if token:
        return token

    raise ValueError(
        f"No GitHub token found.  Set vault provider 'github_contribute' or "
        f"export {env_var}=<your-pat>."
    )


# ---------------------------------------------------------------------------
# Public API
# ---------------------------------------------------------------------------


def submit_pr(
    branch: str,
    findings: list[ScanFinding],
    config: dict[str, Any] | None = None,
    alias: str = "",
    version: str | None = None,
) -> str:
    """Open a Pull Request on ``navig-run/core`` for the self-heal branch.

    **Requires prior explicit user approval.** This function must never be
    called without the user having reviewed and confirmed the patch.

    Args:
        branch: Branch name to use as PR head
            (e.g. ``"navig-selfheal/20260315-a3f8c2d1"``).
        findings: Approved scan findings to include in the PR body.
        config: ``contribute`` config dict from ``navig.yaml``.
        alias: Contributor display name (may be empty).
        version: NAVIG version string; resolved from package metadata when
            omitted.

    Returns:
        HTML URL of the newly created Pull Request.

    Raises:
        ValueError: On API failure, missing token, or invalid config.
    """
    cfg: dict = config or {}
    token = _resolve_token(cfg)
    username = get_github_username(token)

    if version is None:
        try:
            from navig import __version__ as _v  # noqa: PLC0415

            version = _v
        except Exception:  # noqa: BLE001
            version = "unknown"

    body = _build_pr_body(findings, alias=alias, version=version, branch=branch)
    title = _derive_pr_title(findings)

    payload = {
        "title": title,
        "head": f"{username}:{branch}",
        "base": "main",
        "body": body,
        "maintainer_can_modify": True,
    }

    logger.info("Opening PR: {} → {}/main", payload["head"], UPSTREAM_REPO)
    response = _github_request(
        "POST",
        f"/repos/{UPSTREAM_REPO}/pulls",
        token=token,
        json=payload,
    )
    pr_url: str = response["html_url"]
    logger.info("PR created: {}", pr_url)
    return pr_url


def _derive_pr_title(findings: list[ScanFinding]) -> str:
    """Derive a concise PR title from the findings list.

    Args:
        findings: Approved findings.

    Returns:
        PR title string.
    """
    counts: dict[str, int] = {}
    for f in findings:
        counts[f.severity] = counts.get(f.severity, 0) + 1

    parts = []
    for sev in ("critical", "high", "medium", "low"):
        if counts.get(sev, 0):
            parts.append(f"{counts[sev]} {sev}")

    summary = ", ".join(parts) or "code quality"
    return f"fix: self-heal — {summary} issue(s)"
