"""
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 pygittools.validation import check_export_ok

from pygitweb.config import settings


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], int | str | list[str] | bool | None] | 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 isinstance(val, list) and val:
            return val[0]
        if isinstance(val, str) and val:
            return 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."""
    try:
        repo = pygit2.Repository(os.path.join(settings.PROJECTROOT, project))
        if not repo.head:
            return None
        commit = repo[repo.head.target]
        if isinstance(commit, pygit2.Commit):
            return commit.committer.time
    except (KeyError, pygit2.GitError, OSError):
        return None
    return None


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