"""Auto-close a linked Forgejo issue when its Pipeline task is marked done.

Called from the task update path after the task commit.  Looks up the
ForgejoIssueTaskLink for the completed task, derives a conventional-commit
closing comment, posts it, then closes the issue on Forgejo.

Conventional-commit type is inferred from the issue's labels:
  fix       — bug / defect / hotfix / error
  docs      — doc / documentation / readme
  test      — test / testing / spec
  refactor  — refactor / cleanup
  chore     — chore / ci / cd / maintenance / deps
  style     — style / css / design
  feat      — everything else (default)
"""

from __future__ import annotations

from typing import TYPE_CHECKING
from uuid import UUID

from sqlmodel import select

from app.core.logging import get_logger
from app.db import crud
from app.models.agents import Agent
from app.models.boards import Board
from app.models.forgejo_issue_task_links import ForgejoIssueTaskLink
from app.models.forgejo_issues import ForgejoIssue
from app.models.forgejo_repositories import ForgejoRepository
from app.models.tasks import Task
from app.services.forgejo_issue_close import CloseIssueError, close_cached_issue
from app.services.forgejo_issue_comment import PostCommentError, post_comment_on_issue

if TYPE_CHECKING:
    from sqlalchemy.ext.asyncio.session import AsyncSession

logger = get_logger(__name__)

# ── conventional-commit helpers ───────────────────────────────────────────────

_TYPE_RULES: list[tuple[tuple[str, ...], str]] = [
    (("bug", "fix", "defect", "error", "hotfix", "regression"), "fix"),
    (("doc", "docs", "documentation", "readme"), "docs"),
    (("test", "testing", "spec", "coverage"), "test"),
    (("refactor", "cleanup", "clean-up", "restructure"), "refactor"),
    (("chore", "ci", "cd", "maintenance", "deps", "dependency", "dependencies"), "chore"),
    (("style", "css", "design", "ui", "ux", "visual"), "style"),
    (("feat", "feature", "enhancement", "new", "add"), "feat"),
]


def _commit_type(labels: list[dict]) -> str:
    joined = " ".join(
        str(lbl.get("name", "")).lower()
        for lbl in labels
        if isinstance(lbl, dict)
    )
    for keywords, commit_type in _TYPE_RULES:
        if any(kw in joined for kw in keywords):
            return commit_type
    return "feat"


def _commit_scope(repository: ForgejoRepository) -> str:
    return repository.repo.lower()


def _closing_comment(
    *,
    issue: ForgejoIssue,
    repository: ForgejoRepository,
    task: Task,
    board: Board,
    agent: Agent | None,
) -> str:
    commit_type = _commit_type(list(issue.labels or []))
    scope = _commit_scope(repository)
    description = issue.title.strip()
    commit_line = f"{commit_type}({scope}): {description}"

    lines = [
        "Closed automatically by Pipeline — task marked done.",
        "",
        f"```",
        commit_line,
        f"```",
        "",
        f"**Board:** {board.name}",
        f"**Task:** {task.title}",
    ]
    if agent:
        lines.append(f"**Completed by:** {agent.name}")

    return "\n".join(lines)


# ── main entry point ──────────────────────────────────────────────────────────

async def close_linked_issue_on_done(
    session: AsyncSession,
    *,
    task: Task,
    previous_status: str,
) -> None:
    """Close the Forgejo issue linked to `task` if the board rule is enabled.

    No-ops silently when:
    - The task didn't just transition to done
    - The board has auto_close_issue_on_task_done disabled
    - No ForgejoIssueTaskLink exists for this task
    - The issue is already closed
    """
    if task.status != "done" or previous_status == "done":
        return
    if not task.board_id:
        return

    board = await crud.get_by_id(session, Board, task.board_id)
    if board is None or not board.auto_close_issue_on_task_done:
        return

    link = (
        await session.exec(
            select(ForgejoIssueTaskLink).where(
                ForgejoIssueTaskLink.task_id == task.id,
            )
        )
    ).first()
    if link is None:
        return

    issue = await crud.get_by_id(session, ForgejoIssue, link.issue_id)
    if issue is None or issue.state == "closed":
        return

    repository = await crud.get_by_id(session, ForgejoRepository, issue.repository_id)
    if repository is None:
        return

    agent: Agent | None = None
    if task.assigned_agent_id:
        agent = await crud.get_by_id(session, Agent, task.assigned_agent_id)

    comment_body = _closing_comment(
        issue=issue,
        repository=repository,
        task=task,
        board=board,
        agent=agent,
    )

    try:
        await post_comment_on_issue(session, issue=issue, body=comment_body)
    except PostCommentError as exc:
        logger.warning(
            "auto_close_issue.comment_failed",
            extra={
                "task_id": str(task.id),
                "issue_id": str(issue.id),
                "error": str(exc),
            },
        )

    try:
        await close_cached_issue(
            session,
            issue=issue,
            actor_agent_id=task.assigned_agent_id,
        )
        logger.info(
            "auto_close_issue.closed",
            extra={
                "task_id": str(task.id),
                "issue_id": str(issue.id),
                "forgejo_issue_number": issue.forgejo_issue_number,
                "repository": f"{repository.owner}/{repository.repo}",
            },
        )
    except CloseIssueError as exc:
        logger.warning(
            "auto_close_issue.close_failed",
            extra={
                "task_id": str(task.id),
                "issue_id": str(issue.id),
                "error": str(exc),
            },
        )
