"""Helper functions for CI errors retrieval."""

from __future__ import annotations

import re
from pathlib import Path

from aida_mcp.tools.github.ci_errors_models import CiAnnotation
from aida_mcp.utils.artifacts import get_artifacts_root

# Heuristic scoring for error excerpt extraction.
# Keep as a module-level constant so it is easy to review/tune.
_ERROR_ANCHOR_SCORES: tuple[tuple[str, int], ...] = (
    # Python
    ("traceback (most recent call last):", 100),
    ("assertionerror", 80),
    ("pytest", 40),
    ("error:", 30),  # keep low; too generic
    # Gradle / JVM
    ("failure: build failed with an exception.", 95),
    ("what went wrong:", 70),
    ("execution failed for task", 85),
    ("> task :", 60),
    ("caused by:", 80),
    ("exception is:", 70),
    ("build failed", 60),
    ("compilation failed", 70),
    # Node/npm
    ("npm err!", 70),
    ("pnpm", 30),
    ("yarn", 30),
    # GitHub Actions generic marker (avoid if possible)
    ("##[error]", 10),
    ("process completed with exit code", -50),
)

_RE_REPO = re.compile(r"^[^/]+/[^/]+$")
_RE_ANSI = re.compile(r"\x1b\[[0-9;]*[A-Za-z]")


def normalize_message(msg: str) -> str:
    # Flatten newlines so callers can print without huge vertical spam.
    return " ".join(msg.splitlines()).strip()


def strip_ansi(text: str) -> str:
    return _RE_ANSI.sub("", text).replace("\r", "")


def annotations_are_generic(annotations: list[CiAnnotation]) -> bool:
    """Heuristic: detect annotations that don't contain actionable details."""
    if not annotations:
        return True
    msgs = [a.get("message", "").strip() for a in annotations]
    msgs = [m for m in msgs if m]
    if not msgs:
        return True
    generic_markers = ("process completed with exit code",)
    return all(any(marker in m.lower() for marker in generic_markers) for m in msgs)


def extract_error_excerpt(text: str, *, max_lines: int = 60) -> str | None:
    lines = [ln.rstrip("\n") for ln in text.splitlines()]
    if not lines:
        return None

    candidates: list[tuple[int, int]] = []  # (score, idx)
    for i in range(len(lines) - 1, -1, -1):
        low = lines[i].lower()
        score = 0
        for needle, w in _ERROR_ANCHOR_SCORES:
            if needle in low:
                score += w
        if score != 0:
            candidates.append((score, i))

    start_idx: int | None = None
    if candidates:
        # pick best-scoring candidate; if tied, prefer the later one (closer to end)
        best = max(candidates, key=lambda t: (t[0], t[1]))
        if best[0] > 0:
            start_idx = best[1]

    if start_idx is None:
        start_idx = max(0, len(lines) - max_lines)

    # Add small context above the anchor line.
    start_idx = max(0, start_idx - 8)

    excerpt_lines = lines[start_idx:]
    if len(excerpt_lines) > max_lines:
        excerpt_lines = excerpt_lines[-max_lines:]
    excerpt = "\n".join(excerpt_lines).strip()
    return excerpt or None


def validate_repo(repo: str) -> None:
    if not _RE_REPO.match(repo):
        raise ValueError("repo must be in 'owner/repo' format")


def default_log_dir() -> Path:
    return get_artifacts_root() / "reports" / "github_ci_errors"


def sanitize_filename_fragment(value: str) -> str:
    safe = re.sub(r"[^A-Za-z0-9_.-]+", "_", value.strip())
    return safe.strip("._") or "log"


def write_log_file(*, base_dir: Path, repo: str, pr_number: int, check_run_id: int, name: str, text: str) -> str:
    repo_dir = base_dir / repo.replace("/", "__") / f"pr_{pr_number}"
    repo_dir.mkdir(parents=True, exist_ok=True)
    fname = f"check_{check_run_id}__{sanitize_filename_fragment(name)}.log"
    path = repo_dir / fname
    path.write_text(text, encoding="utf-8", errors="replace")
    return str(path)
