"""
Project list: get_projects_list, search_projects_list, project_in_list,
get_project_owner, get_project_list_from_file, get_last_activity.
Ported from gitweb/gitweb.perl.
"""

from __future__ import annotations

import os
import re
from collections.abc import Callable
from typing import Any

import pygit2

from pygitweb.config import settings
from pygitweb.validation import check_export_ok


def _export_ok_path(git_dir: str, export_ok: str) -> bool:
	return not export_ok or os.path.isfile(os.path.join(git_dir, export_ok))


def project_in_list(
	project: str,
	get_projects_list_fn: Callable[[], list[dict[str, Any]]],
) -> bool:
	"""True if project appears in project list. Port of project_in_list."""
	lst = get_projects_list_fn()
	return any(p.get("path") == project for p in lst)


def _find_projects_in_dir(
	root: str,
	prefix_len: int,
	prefix_depth: int,
	maxdepth: int,
	export_ok: str,
	export_auth_hook: Callable[[str], bool] | None,
	skip_export_check: bool = False,
) -> list[dict[str, Any]]:
	result = []
	for dirpath, dirnames, _ in os.walk(root, topdown=True):
		rel = os.path.relpath(dirpath, root)
		depth = 0 if rel == "." else rel.count(os.sep) + 1
		if depth >= maxdepth:
			dirnames.clear()
			continue
		for d in list(dirnames):
			path = os.path.join(dirpath, d)
			if not os.path.isdir(path):
				continue
			try:
				if not os.access(path, os.X_OK):
					continue
			except OSError:
				continue
			repo = pygit2.discover_repository(path)
			if not repo:
				continue
			project_path = os.path.relpath(path, settings.PROJECTROOT)
			project_path = project_path.replace("\\", "/")
			git_dir = os.path.join(settings.PROJECTROOT, project_path)
			if not skip_export_check and not check_export_ok(git_dir, export_ok, export_auth_hook):
				continue
			result.append({"path": project_path})
			dirnames.remove(d)
	return result


def git_get_projects_list(
	filter_path: str = "",
	paranoid: bool = False,
	projectroot: str | None = None,
	projects_list: str | None = None,
	project_maxdepth: int | None = None,
	export_ok: str = "",
	export_auth_hook: Callable[[str], bool] | None = None,
) -> list[dict[str, Any]]:
	"""List projects from directory scan or file. Port of git_get_projects_list."""
	projectroot = projectroot if projectroot is not None else settings.PROJECTROOT
	projects_list = projects_list if projects_list is not None else settings.PROJECTS_LIST
	project_maxdepth = project_maxdepth if project_maxdepth is not None else settings.PROJECT_MAXDEPTH
	if os.path.isdir(projects_list):
		root = projects_list.rstrip("/")
		prefix_len = len(root) + 1
		prefix_depth = root.count(os.sep)
		if filter_path and not paranoid:
			root = os.path.join(root, filter_path).rstrip("/")
		result = _find_projects_in_dir(
			root,
			prefix_len,
			prefix_depth,
			project_maxdepth,
			export_ok,
			export_auth_hook,
			skip_export_check=settings.LIST_ALL,
		)
		if filter_path and paranoid:
			result = [p for p in result if p["path"].startswith(filter_path + "/")]
		return result
	if os.path.isfile(projects_list):
		from urllib.parse import unquote

		result = []
		with open(projects_list) as f:
			for line in f:
				line = line.strip()
				if not line:
					continue
				parts = line.split(None, 1)
				path = unquote(parts[0]) if parts else ""
				owner = unquote(parts[1]) if len(parts) > 1 else None
				if not path:
					continue
				if filter_path and not path.startswith(filter_path + "/"):
					continue
				git_dir = os.path.join(projectroot, path)
				if not settings.LIST_ALL and not check_export_ok(git_dir, export_ok, export_auth_hook):
					continue
				pr = {"path": path}
				if owner:
					pr["owner"] = owner
				result.append(pr)
		return result
	return []


_gitweb_project_owner: dict[str, str] | None = None


def git_get_project_list_from_file(
	projects_list: str | None = None,
	projectroot: str | None = None,
) -> dict[str, str]:
	"""Load project -> owner from file. Port of git_get_project_list_from_file."""
	global _gitweb_project_owner
	if _gitweb_project_owner is not None:
		return _gitweb_project_owner
	projects_list = projects_list if projects_list is not None else settings.PROJECTS_LIST
	_gitweb_project_owner = {}
	if os.path.isfile(projects_list):
		from urllib.parse import unquote

		with open(projects_list) as f:
			for line in f:
				line = line.strip()
				if not line:
					continue
				parts = line.split(None, 1)
				path = unquote(parts[0]) if parts else ""
				owner = unquote(parts[1]) if len(parts) > 1 else ""
				if path:
					_gitweb_project_owner[path] = owner
	return _gitweb_project_owner


def git_get_project_owner(
	project: str,
	projectroot: str | None = None,
	get_project_config: Callable[[str, str], Any] | None = None,
) -> str | None:
	"""Owner from list file or config or file ownership. Port of git_get_project_owner."""
	if not project:
		return None
	projectroot = projectroot if projectroot is not None else settings.PROJECTROOT
	owners = git_get_project_list_from_file(settings.PROJECTS_LIST, projectroot)
	if project in owners and owners[project]:
		return owners[project]
	if get_project_config:
		val = get_project_config(project, "owner")
		if val:
			return val[0] if isinstance(val, list) else val
	return None


def git_get_last_activity(project: str, projectroot: str | None = None) -> int | None:
	"""Last commit timestamp for project. Port of git_get_last_activity."""
	from pygitweb.git_helpers import git_get_head_hash, parse_commit

	oid = git_get_head_hash(project)
	if not oid:
		return None
	co = parse_commit(project, oid)
	return co.get("committer_epoch")


def search_projects_list(
	projlist: list[dict[str, Any]],
	tagfilter: str | None = None,
	search_regexp: str | None = None,
	fill_project_list_info: Callable[..., None] | None = None,
) -> list[dict[str, Any]]:
	"""Filter by tag or search regex. Port of search_projects_list."""
	if not tagfilter and not search_regexp:
		return projlist
	if fill_project_list_info:
		fill_project_list_info(projlist, tagfilter=tagfilter, search_re=search_regexp)
	result = []
	for pr in projlist:
		if tagfilter:
			ctags = pr.get("ctags") or {}
			if not any(k.lower() == tagfilter.lower() for k in ctags):
				continue
		if search_regexp:
			try:
				rex = re.compile(search_regexp)
			except re.error:
				continue
			descr = (pr.get("descr_long") or "") + (pr.get("path") or "")
			if not rex.search(descr):
				continue
		result.append(pr)
	return result