"""Client-side reference manifest for board task @ / # lookups."""

from __future__ import annotations

import os
from datetime import UTC, datetime, timedelta, timezone
from typing import TypedDict
from urllib.parse import quote

from pygit2 import Oid, Repository, Tag
from pygittools.merge import MR_REF_PREFIX, get_merge_request_by_oid

from pygitweb.config import settings
from pygitweb.git_helpers import get_commit_history
from pygitweb.tasks import TASK_REF_PREFIX, get_board_tasks_grouped

BOARD_REF_MANIFEST_COMMIT_LIMIT = 100


class BoardRefManifestEntry(TypedDict):
    name: str
    insert: str
    url: str
    tags: str
    snippet: str
    created: str


class BoardRefManifest(TypedDict):
    commits: list[BoardRefManifestEntry]
    refs: list[BoardRefManifestEntry]


def _format_epoch(epoch: int | None, tz_offset: int | None = None) -> str:
    if epoch is None:
        return ""
    try:
        tz = timezone(timedelta(seconds=tz_offset * 60)) if tz_offset is not None else UTC
        return datetime.fromtimestamp(epoch, tz=tz).strftime("%Y-%m-%d %H:%M:%S")
    except (ValueError, OSError):
        return ""


def _project_url(project: str) -> str:
    return f"/project/{quote(project, safe='/')}"


def _board_task_url(project: str, board_name: str, task_ref: str) -> str:
    base = f"{_project_url(project)}/board/"
    qs = f"b={quote(board_name, safe='')}&task={quote(task_ref, safe='')}"
    return f"{base}?{qs}" if board_name != "Tasks" else f"{base}?task={quote(task_ref, safe='')}"


def _task_display_id(ref: str) -> str:
    slug = ref.removeprefix(TASK_REF_PREFIX)
    if slug.startswith("task_"):
        return slug.removeprefix("task_")
    return slug


def _manifest_commits(project: str) -> list[BoardRefManifestEntry]:
    project_url = _project_url(project)
    entries: list[BoardRefManifestEntry] = []
    for commit in get_commit_history(project, ref=None, max_count=BOARD_REF_MANIFEST_COMMIT_LIMIT):
        oid = str(commit.get("oid") or "")
        if not oid:
            continue
        short = oid[:7]
        subject = str(commit.get("subject") or "")
        author = str(commit.get("author") or "")
        created = _format_epoch(commit.get("author_epoch"), commit.get("author_tz"))
        search_tags = " ".join(part.lower() for part in (oid, short, subject, author) if part)
        entries.append({
            "name": f"{short} {subject}".strip(),
            "insert": short,
            "url": f"{project_url}?a=commit&h={quote(oid, safe='')}",
            "tags": search_tags,
            "snippet": author,
            "created": created,
        })
    return entries


def _manifest_board_tasks(project: str, board_name: str) -> list[BoardRefManifestEntry]:
    entries: list[BoardRefManifestEntry] = []
    seen_refs: set[str] = set()
    try:
        columns = get_board_tasks_grouped(project, board_name)
    except KeyError:
        return entries
    for column in columns:
        for task in column["tasks"]:
            task_ref = str(task.get("ref") or "")
            if not task_ref or task_ref in seen_refs:
                continue
            seen_refs.add(task_ref)
            task_id = str(task.get("id") or _task_display_id(task_ref))
            title = str(task.get("title") or "Untitled")
            slug = task_ref.removeprefix("refs/tags/")
            created = str(task.get("updated_at") or task.get("created_at") or "")
            search_tags = " ".join(
                part.lower() for part in (task_id, slug, task_ref, title, str(task.get("status") or "")) if part
            )
            entries.append({
                "name": title,
                "insert": task_id,
                "url": _board_task_url(project, board_name, task_ref),
                "tags": search_tags,
                "snippet": f"task {task_id}",
                "created": created,
            })
    return entries


def _manifest_merge_requests(project: str) -> list[BoardRefManifestEntry]:
    project_url = _project_url(project)
    repo = Repository(os.path.join(settings.PROJECTROOT, project))
    entries: list[BoardRefManifestEntry] = []
    for ref_name in repo.references:
        if not ref_name.startswith(MR_REF_PREFIX):
            continue
        try:
            oid = repo.references[ref_name].resolve().target
            obj = repo[oid]
            if not isinstance(obj, Tag):
                continue
            mr = get_merge_request_by_oid(repo, Oid(hex=str(oid)))
        except (ValueError, KeyError, TypeError):
            continue
        slug = ref_name.removeprefix(MR_REF_PREFIX)
        title = mr.title or slug
        created = mr.updated_at.isoformat() if mr.updated_at else ""
        search_tags = " ".join(
            part.lower() for part in (slug, ref_name, title, mr.description or "", mr.ours or "") if part
        )
        entries.append({
            "name": title,
            "insert": slug,
            "url": f"{project_url}?a=tag&h={quote(ref_name, safe='')}",
            "tags": search_tags,
            "snippet": f"merge request {slug}",
            "created": created,
        })
    return entries


def board_ref_manifest(project: str, board_name: str) -> BoardRefManifest:
    refs = _manifest_board_tasks(project, board_name)
    refs.extend(_manifest_merge_requests(project))
    refs.sort(key=lambda entry: (entry["created"], entry["name"]), reverse=True)
    return {
        "commits": _manifest_commits(project),
        "refs": refs,
    }