# gophian -- tools to help with Debianizing Go software
# Copyright (C) 2024-2025 Maytham Alsudany <maytha8thedev@gmail.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <https://www.gnu.org/licenses/>.

import subprocess
from os import PathLike
from pathlib import Path
from typing import List, Self

from gophian.error import GitExecutionError, GophianError


class GitRunner:
    """
    Class to run git commands.
    """

    def __init__(self, **kwargs) -> None:
        self.quiet: bool = kwargs.get("quiet", True)

    def run(self, args: List[str], **kwargs) -> subprocess.CompletedProcess[bytes]:
        args = ["git"] + args
        quiet = kwargs.get("quiet", self.quiet)
        supports_quiet = kwargs.get("supports_quiet", False)
        if supports_quiet and quiet:
            args.insert(2, "--quiet")
        if "quiet" in kwargs:
            kwargs.pop("quiet")
        if "supports_quiet" in kwargs:
            kwargs.pop("supports_quiet")
        out = subprocess.run(args, **kwargs)
        if out.returncode != 0:
            raise GitExecutionError(out)
        return out

    def get(self, args: List[str], **kwargs) -> str:
        kwargs["capture_output"] = True
        return self.run(args, **kwargs).stdout.strip().decode()


class Git(GitRunner):
    """
    A local git repository.
    """

    def __init__(self, path: PathLike, **kwargs) -> None:
        super().__init__(**kwargs)
        self.path = Path(path)
        check: bool = kwargs.get("check", True)
        if check:
            if not self.path.exists():
                raise FileNotFoundError(self.path)
            if not ((self.path / ".git").exists() and (self.path / ".git").is_dir()):
                raise NotAGitRepoError(str(self.path))
        pass

    def run(self, args: List[str], **kwargs):
        return super().run(args, cwd=self.path, **kwargs)

    def _clone(self, repo_url: str, **kwargs):
        args = ["clone", repo_url, str(self.path)]
        if branch := kwargs.get("branch"):
            args += ["--branch", branch, "-c", "advice.detachedHead=false"]
        if depth := kwargs.get("depth"):
            args += ["--depth", str(depth)]
        super().run(args, supports_quiet=True)

    @classmethod
    def clone(cls, repo_url: str, dest: PathLike, **kwargs) -> Self:
        path = Path(dest).resolve()
        if not path.exists() or not path.is_dir():
            path.mkdir()
        repo = cls(path, quiet=kwargs.get("quiet", False), check=False)
        repo._clone(repo_url, **kwargs)
        return repo

    @classmethod
    def init(cls, dest: PathLike, branch: str = "main", **kwargs) -> Self:
        path = Path(dest).resolve()
        if not path.exists() or not path.is_dir():
            path.mkdir()
        repo = cls(path, quiet=kwargs.get("quiet", False), check=False)
        repo.run(["init", "-b", branch], supports_quiet=True)
        repo = cls(path)
        return repo

    def add(self, paths: List[str] = ["."]) -> None:
        self.run(["add"] + paths)

    def commit(self, msg: str, **kwargs) -> None:
        cmd = ["commit", "-m", msg]
        if kwargs.get("gpg_sign") is not None:
            cmd.append("--gpg-sign" if kwargs.get("gpg_sign") else "--no-gpg-sign")
        self.run(cmd, supports_quiet=True)

    def tags(self) -> List[str]:
        return self.get(["tag", "--list"], quiet=False).splitlines()

    def checkout(self, commitish: str) -> None:
        self.run(["checkout", commitish], supports_quiet=True)

    def remote(self, url: str, name: str = "origin") -> None:
        self.run(["remote", "add", name, url])

    def fetch(self, remote: str = "origin") -> None:
        self.run(["fetch", remote])

    @staticmethod
    def remote_tags(url: str, **kwargs) -> List[str]:
        runner = GitRunner(quiet=kwargs.get("quiet"))
        return [
            line.split("/")[2]
            for line in runner.get(["ls-remote", "--tags", "--refs", url]).splitlines()
        ]


class NotAGitRepoError(GophianError):
    def __init__(self, path: str) -> None:
        super().__init__(f"'{path}' is not a git repo (no .git directory found)")
