"""
Per-project timeline cache for recent repository activity.
"""

from __future__ import annotations

from typing import Literal, TypedDict

import pygit2

from pygitweb.config import settings
from pygitweb.git_helpers import open_repo
from pygitweb.projects import git_get_projects_list

TIMELINE_CACHE_SIZE: int = 10


class TimelineEvent(TypedDict):
	timestamp: int
	oid: str
	kind: Literal["commit", "ref"]
	description: str | None


class TimelineProjectEvent(TypedDict):
	project: str
	event: TimelineEvent


PROJECT_TIMELINE_CACHE: dict[str, list[TimelineEvent]] = {}


def _commit_description(commit: pygit2.Commit) -> str | None:
	if not commit.message:
		return None
	first_line: str = commit.message.splitlines()[0].strip()
	return first_line or None


def _collect_commit_events(repo: pygit2.Repository) -> list[TimelineEvent]:
	start_points: list[pygit2.Oid] = []
	for ref_name in repo.references:
		if not ref_name.startswith("refs/heads/"):
			continue
		try:
			resolved: pygit2.Reference = repo.references[ref_name].resolve()
			oid: pygit2.Oid = resolved.target
			start_points.append(oid)
		except (KeyError, pygit2.GitError, ValueError):
			continue
	if not start_points:
		try:
			head_ref: pygit2.Reference = repo.head
			start_points.append(head_ref.target)
		except (pygit2.GitError, ValueError, KeyError):
			pass
	if not start_points:
		return []

	walker: pygit2.Walker = repo.walk(
		start_points[0],
		pygit2.GIT_SORT_TIME | pygit2.GIT_SORT_TOPOLOGICAL,
	)
	for oid in start_points[1:]:
		try:
			walker.push(oid)
		except (pygit2.GitError, ValueError):
			continue

	events: list[TimelineEvent] = []
	seen: set[str] = set()
	for commit in walker:
		commit_oid: str = str(commit.id)
		if commit_oid in seen:
			continue
		seen.add(commit_oid)
		events.append({
			"timestamp": commit.commit_time,
			"oid": commit_oid,
			"kind": "commit",
			"description": _commit_description(commit),
		})
	return events


def _collect_ref_events(repo: pygit2.Repository) -> list[TimelineEvent]:
	events: list[TimelineEvent] = []
	for ref_name in repo.references:
		if not ref_name.startswith("refs/tags/"):
			continue
		try:
			ref_obj: pygit2.Reference = repo.references[ref_name].resolve()
			oid_str: str = str(ref_obj.target)
			obj = repo[ref_obj.target]
		except (KeyError, pygit2.GitError, ValueError):
			continue

		if isinstance(obj, pygit2.Tag):
			tag_tagger = obj.tagger
			timestamp: int = tag_tagger.time if tag_tagger else 0
			description: str | None = ref_name.replace("refs/tags/", "", 1)
		elif isinstance(obj, pygit2.Commit):
			timestamp = obj.commit_time
			description = ref_name.replace("refs/tags/", "", 1)
		else:
			continue

		events.append({
			"timestamp": timestamp,
			"oid": oid_str,
			"kind": "ref",
			"description": description or None,
		})
	return events


def build_project_timeline_cache(project: str, max_events: int = TIMELINE_CACHE_SIZE) -> list[TimelineEvent]:
	try:
		repo: pygit2.Repository = open_repo(project)
	except (pygit2.GitError, OSError, ValueError):
		return []

	commit_events: list[TimelineEvent] = _collect_commit_events(repo)
	ref_events: list[TimelineEvent] = _collect_ref_events(repo)
	all_events: list[TimelineEvent] = commit_events + ref_events
	all_events.sort(key=lambda event: (event["timestamp"], event["oid"]), reverse=True)
	return all_events[:max_events]


def warm_timeline_cache() -> None:
	global PROJECT_TIMELINE_CACHE
	new_cache: dict[str, list[TimelineEvent]] = {}
	projects = git_get_projects_list(
		filter_path="",
		paranoid=settings.STRICT_EXPORT,
		export_ok=settings.EXPORT_OK,
	)
	for project_entry in projects:
		project_name = project_entry.get("path")
		if not isinstance(project_name, str) or not project_name:
			continue
		try:
			new_cache[project_name] = build_project_timeline_cache(project_name)
		except (pygit2.GitError, OSError, ValueError, KeyError):
			new_cache[project_name] = []
	PROJECT_TIMELINE_CACHE = new_cache


def get_project_timeline_cache(project: str) -> list[TimelineEvent]:
	return PROJECT_TIMELINE_CACHE.get(project, [])


def get_all_timeline_events() -> list[TimelineProjectEvent]:
	events: list[TimelineProjectEvent] = []
	for project_name, project_events in PROJECT_TIMELINE_CACHE.items():
		for event in project_events:
			events.append({"project": project_name, "event": event})
	events.sort(
		key=lambda item: (item["event"]["timestamp"], item["event"]["oid"], item["project"]),
		reverse=True,
	)
	return events