#!/usr/bin/env python3

import argparse
from dataclasses import dataclass
import re
import subprocess
from pathlib import Path

REMOTE_TYPES = {
    "github": {
        "match": re.compile(r'git@github.com:(?P<username>[\w\.-]+)/(?P<project>[\w\.-]+).git'),
        "format-branch": lambda g: f"https://github.com/{g.username}/{g.project}/tree/{g.branch}/",
        "format-branch-file": lambda g: f"https://github.com/{g.username}/{g.project}/blob/{g.branch}/{g.file}",
        "format-branch-dir": lambda g: f"https://github.com/{g.username}/{g.project}/tree/{g.branch}/{g.dir}",
        "format-commit": lambda g: f"https://github.com/{g.username}/{g.project}/commit/{g.commit}/",
        "format-commit-file": lambda g: f"https://github.com/{g.username}/{g.project}/blob/{g.commit}/{g.file}",
        "format-commit-dir": lambda g: f"https://github.com/{g.username}/{g.project}/tree/{g.commit}/{g.dir}",
    },
    "gitea": {
        "match": re.compile(r'(?P<gituser>[\w\.-]+)@(?P<host>[\w\.-]+):(?P<username>[\w\.-]+)/(?P<project>[\w\.-]+).git'),
        "format-branch": lambda g: f"https://{g.host}/{g.username}/{g.project}/src/branch/{g.branch}/",
        "format-branch-file": lambda g: f"https://{g.host}/{g.username}/{g.project}/src/branch/{g.branch}/{g.file}",
        "format-branch-dir": lambda g: f"https://{g.host}/{g.username}/{g.project}/src/branch/{g.branch}/{g.dir}",
        "format-commit": lambda g: f"https://{g.host}/{g.username}/{g.project}/commit/{g.commit}/",
        "format-commit-file": lambda g: f"https://{g.host}/{g.username}/{g.project}/src/commit/{g.commit}/{g.file}",
        "format-commit-dir": lambda g: f"https://{g.host}/{g.username}/{g.project}/src/commit/{g.commit}/{g.dir}",
    },
}

@dataclass
class FormatArgs:
    gituser: str = None
    host: str = None
    username: str = None
    project: str = None
    commit: str = None
    branch: str = None
    file: str = None
    dir: str = None

def is_git_repo():
    s = subprocess.run(["git", "rev-parse"], capture_output=True, text=True)

    return s.returncode == 0

def get_git_dir():
    s = subprocess.run(["git", "rev-parse", "--show-toplevel"], capture_output=True, text=True)
    return Path(s.stdout.strip())

def get_remote_branch():
    s = subprocess.run(["git", "status", "--porcelain", "-uno", "-b", "--no-ahead-behind"], capture_output=True, text=True)

    if s.stdout.startswith("## HEAD (no branch)"):
        print("Detached head, can't link")
        exit(1)

    git_status_branch_info = s.stdout.splitlines()[0][3:].split()[0]

    branches = git_status_branch_info.split("...")

    if len(branches) != 2:
        raise Exception("no branch name found")

    local_branch, remote_branch = branches

    remote, branch = remote_branch.split("/", maxsplit=1)

    return {
        "remote": remote,
        "branch": branch,
    }

def get_remote_url(remote):
    s = subprocess.run(["git", "remote", "get-url", remote], capture_output=True, text=True)

    remote_url = s.stdout.strip()

    return remote_url

def get_last_commit():
    s = subprocess.run(["git", "rev-parse", "HEAD"], capture_output=True, text=True)

    commit = s.stdout.strip()

    return commit

def main():
    parser = argparse.ArgumentParser(
        prog='git-show-link',
    )

    parser.add_argument("path", nargs="?", default=None, help="Path to link to specific file or directory")
    parser.add_argument("--branch", dest="display_branch", action='store_true', help="Display link to branch, instead to commit")
    parser.add_argument("--remote-type", dest="remote_type", choices=REMOTE_TYPES.keys(), help="Specify remote type")

    args = parser.parse_args()

    if not is_git_repo():
        print("Not a git repo")

        exit(1)

    git_dir_path = get_git_dir()

    r = get_remote_branch()

    remote_url = get_remote_url(r["remote"])

    selected_remote_types = REMOTE_TYPES

    if args.remote_type is not None:
        selected_remote_types = {
            args.remote_type: REMOTE_TYPES[args.remote_type],
        }

    remote_type_found = False

    for remote_type_name, remote_type in selected_remote_types.items():
        m = remote_type["match"].match(remote_url)

        if m is None:
            continue

        remote_type_found = True

        g = FormatArgs(**m.groupdict())

        if args.path is not None:
            path = Path(args.path).absolute()
            path = path.relative_to(git_dir_path)

            if path.is_dir():
                path = str(path)

                if path == ".":
                    path = ""
                else:
                    path += "/"

                g.dir = path
            else:
                g.file = str(path)

        if g.file is not None:
            if args.display_branch:
                g.branch = r["branch"]
                print(remote_type["format-branch-file"](g))
            else:
                commit = get_last_commit()
                g.commit = commit
                print(remote_type["format-commit-file"](g))
        elif g.dir is not None:
            if args.display_branch:
                g.branch = r["branch"]
                print(remote_type["format-branch-dir"](g))
            else:
                commit = get_last_commit()
                g.commit = commit
                print(remote_type["format-commit-dir"](g))
        else:
            if args.display_branch:
                g.branch = r["branch"]
                print(remote_type["format-branch"](g))
            else:
                commit = get_last_commit()
                g.commit = commit
                print(remote_type["format-commit"](g))

        break

    if not remote_type_found:
        print("No remote type matched")
        exit(1)


if __name__ == "__main__":
    main()