# 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 re
import subprocess
import xml.etree.ElementTree as ET
from datetime import datetime
from enum import Enum, auto
from pathlib import Path
from shutil import move
from typing import List, Optional, Tuple, cast
from urllib.parse import urlparse

import click
import questionary
import requests
from jinja2 import Environment, PackageLoader, select_autoescape

from gophian.__about__ import BUGS_URL
from gophian.error import GbpExecutionError, UscanExecutionError
from gophian.git import Git
from gophian.hosts import KNOWN_HOSTS
from gophian.name import debianize_name, shorten_host
from gophian.packages import DebianGolangPackages
from gophian.salsa import salsa_repo_exists
from gophian.session import Session
from gophian.session.git import GoPackage
from gophian.vcs import get_short_desc, package_from_import_path
from gophian.wnpp import ITPBug, RFPBug


@click.command()
@click.argument("importpath")
@click.option("--quiet/--no-quiet", default=False, help="Don't print warnings.")
@click.option(
    "--type",
    "package_type_opt",
    required=False,
    type=click.Choice(["l", "l+b", "b+l", "b"], case_sensitive=False),
    help="Type of software being packaged.",
)
@click.option(
    "--name",
    required=True,
    envvar="DEBFULLNAME",
    help="Name of package maintainer. Uses DEBFULLNAME environment variable by default.",
)
@click.option(
    "--email",
    required=True,
    envvar="DEBEMAIL",
    help="Email of package maintainer. Uses DEBEMAIL environment variable by default.",
)
@click.option(
    "--warn-packaged/--no-warn-packaged",
    default=True,
    help="Warn if the given import path is already packaged in Debian.",
)
@click.option(
    "--warn-salsa-repo/--no-warn-salsa-repo",
    default=True,
    help="Warn if the package already has a repo under the Go team namespace on Salsa.",
)
@click.option(
    "--itp/--no-itp",
    default=True,
    help="Generate an ITP template as well.",
    envvar="GOPHIAN_ITP",
)
@click.option(
    "--todo/--no-todo",
    default=False,
    help="Generate a debian/TODO file.",
    envvar="GOPHIAN_TODO",
)
@click.option(
    "--pristine-tar/--no-pristine-tar",
    default=False,
    help="Create a pristine-tar branch and delta file.",
    envvar="GOPHIAN_PRISTINE_TAR",
)
@click.option(
    "--git-remote",
    default="ssh",
    type=click.Choice(["ssh", "https"], case_sensitive=False),
    help="Protocol to use when setting Git remote.",
    envvar="GOPHIAN_GIT_REMOTE",
)
@click.option(
    "--gpg-sign/--no-gpg-sign",
    default=None,
    envvar="GOPHIAN_GPG_SIGN",
    help=(
        "GPG sign the commit that merges the upstream tarball into the"
        "packaging branch. If not passed to Gophian, then it will not be passed"
        "to git nor gbp and they will use their configured defaults."
    ),
)
@click.option(
    "--upstream-git-history/--no-upstream-git-history",
    default=False,
    envvar="GOPHIAN_UPSTREAM_GIT_HISTORY",
    help=(
        "Include upstream git history. Upstream will be tracked with the"
        "'upstreamvcs' remote, and commits that import new package versions"
        "will include the upstream commit or tag as a parent."
    ),
)
@click.pass_context
def make(
    context: click.Context,
    importpath: str,
    quiet: bool,
    package_type_opt: Optional[str],
    name: str,
    email: str,
    warn_packaged: bool,
    warn_salsa_repo: bool,
    itp: bool,
    todo: bool,
    pristine_tar: bool,
    git_remote: str,
    gpg_sign: Optional[bool],
    upstream_git_history: bool,
):
    """
    Prepare a Go module for packaging in Debian.
    """

    requests_session: requests.Session = context.obj

    if not quiet:
        click.secho("gophian is experimental software!", bold=True, fg="yellow")
        click.secho("Please report any problems to:", fg="yellow")
        click.secho(BUGS_URL, fg="yellow")

    with MakeSession(requests_session) as session:
        try:
            go_package, git_repo = package_from_import_path(
                requests_session, importpath
            )
        except Exception as error:
            click.echo(error)
            click.secho("Did you specify a Go package import path?", fg="yellow")
            context.exit(1)

        if go_package != importpath:
            click.echo(
                f"Continuing with repo root {go_package} instead of {importpath}"
            )

        debian_packages = DebianGolangPackages(requests_session)

        if debian_packages._check_for_package(go_package):
            if warn_packaged:
                if not quiet:
                    click.secho(
                        "Pass --no-warn-packaged to ignore this warning and continue.",
                        fg="cyan",
                    )
                return

        host = go_package.lower().split("/")[0]
        if host in KNOWN_HOSTS:
            short_host = KNOWN_HOSTS[host]
        else:
            short_host = shorten_host(host)
            if not quiet:
                click.secho(
                    f"Using '{short_host}' as canoncial hostname for '{host}'",
                    fg="yellow",
                )
                click.secho(
                    "If this is not suitable, please change it manually and report the issue to https://salsa.debian.org/Maytha8/gophian/issues",
                    fg="yellow",
                )

        debian_source_package = debianize_name(go_package, short_host)

        debian_library_package = debian_source_package + "-dev"

        try:
            all_binaries: List[str] = session.package_main_modules(go_package)
        except Exception:
            all_binaries: List[str] = session.find_main_modules(go_package)

        if not quiet and len(all_binaries) > 0:
            click.echo("Found binaries:")
            for binary in all_binaries:
                click.echo(f" - {binary.split('/')[-1]} ({binary})")

        wnpp = None

        binary_word = "binaries" if len(all_binaries) > 1 else "binary"

        if package_type_opt is not None:
            package_type: PackageType = PackageType.from_opt(package_type_opt)
        elif len(all_binaries) == 0:
            package_type: PackageType = PackageType.LIBRARY
        else:
            package_type: PackageType = questionary.select(
                "What type of software are you packaging?",
                choices=[
                    questionary.Choice(
                        "Library only", value=PackageType.LIBRARY, checked=True
                    ),
                    questionary.Choice(
                        f"Library and accompanying {binary_word}",
                        value=PackageType.LIBRARY_AND_BINARY,
                    ),
                    questionary.Choice(
                        f"{binary_word.capitalize()} and accompanying library",
                        value=PackageType.BINARY_AND_LIBRARY,
                    ),
                    questionary.Choice(
                        f"{binary_word.capitalize()} only", value=PackageType.BINARY
                    ),
                ],
            ).ask()
            if package_type is None:
                raise click.Abort()

        selected_binaries: List[str] = []

        if package_type.has_binary():
            if len(all_binaries) > 1:
                binary_choices: List[questionary.Choice] = list(
                    map(
                        lambda b: questionary.Choice(
                            f"{b.split('/')[-1]} ({b})", value=b
                        ),
                        all_binaries,
                    )
                )
                selected_binaries = questionary.checkbox(
                    "Which binaries would you like to include?", choices=binary_choices
                ).ask()
                if selected_binaries is None:
                    raise click.Abort()
                if len(selected_binaries) == 0:
                    package_type = PackageType.LIBRARY
                    click.secho(
                        "You have not selected any binaries; switching package type to library only.",
                        fg="yellow",
                    )
            else:
                selected_binaries = all_binaries

        if (
            package_type == PackageType.BINARY_AND_LIBRARY
            or package_type == PackageType.BINARY
        ):
            choices: List[str] = list(
                map(lambda b: b.split("/")[-1], selected_binaries)
            )

            if git_repo.split("/")[-1] not in choices:
                choices.insert(0, git_repo.split("/")[-1])

            choices.insert(0, debian_source_package)

            debian_source_package = questionary.select(
                "What do you want to name the source package?", choices=choices
            ).ask()
            if debian_source_package is None:
                raise click.Abort()
        if not quiet and salsa_repo_exists(
            requests_session, "go-team/packages/" + debian_source_package
        ):
            click.secho(
                f"Salsa repo of the same name exists for '{debian_source_package}'.",
                fg="yellow",
            )
            click.echo(
                f"https://salsa.debian.org/go-team/packages/{debian_source_package}"
            )
            if warn_salsa_repo:
                click.secho("To ignore, pass the --no-warn-salsa-repo flag.", fg="cyan")
                return

        if bug := ITPBug.get(requests_session, debian_source_package):
            wnpp = bug
            if not quiet:
                click.secho(f"Found wnpp bug for {debian_source_package}", fg="cyan")
                click.secho(
                    f"[#{bug.id}] ITP: {bug.package} -- {bug.description}", fg="cyan"
                )
        elif bug := RFPBug.get(requests_session, debian_source_package):
            wnpp = bug
            if not quiet:
                click.secho(f"Found wnpp bug for {debian_source_package}", fg="cyan")
                click.secho(
                    f"[#{bug.id}] RFP: {bug.package} -- {bug.description}", fg="cyan"
                )

        work_dir = Path(debian_source_package)

        if work_dir.exists():
            click.secho(
                f"Output directory '{work_dir}' already exists",
                fg="red",
            )
            context.exit(1)

        work_dir.mkdir()

        env = Environment(
            loader=PackageLoader("gophian"),
            autoescape=select_autoescape(),
            trim_blocks=True,
            lstrip_blocks=True,
            keep_trailing_newline=True,
        )

        version, repack, watchfile, tarball, package = session.prepare_upstream(
            env,
            go_package,
            debian_source_package,
        )

        tarball = move(tarball, work_dir.parent / tarball.name)

        repo = Git.init(work_dir, "debian/latest", quiet=quiet)
        if git_remote == "ssh":
            repo.remote(
                f"git@salsa.debian.org:go-team/packages/{debian_source_package}.git"
            )
        elif git_remote == "https":
            repo.remote(
                f"https://salsa.debian.org/go-team/packages/{debian_source_package}.git"
            )

        if upstream_git_history:
            repo.remote(f"{package.repo}.git", "upstreamvcs")
            repo.fetch("upstreamvcs")

        cmd = [
            "gbp",
            "import-orig",
            "--no-interactive",
            "--debian-branch=debian/latest",
            "--upstream-branch=upstream/latest",
            "--filter=/debian/**/*",
            "--pristine-tar" if pristine_tar else "--no-pristine-tar",
        ]

        if upstream_git_history:
            cmd.append(f"--upstream-vcs-tag={package.commitish}")

        if gpg_sign is not None:
            cmd.append("--sign-tags" if gpg_sign else "--no-sign-tags")

        cmd.append(tarball.absolute())

        out = subprocess.run(
            cmd,
            stdout=subprocess.DEVNULL,
            stderr=subprocess.DEVNULL,
            cwd=repo.path,
        )
        if out.returncode != 0:
            raise GbpExecutionError(out)

        debian_deps = []
        debian_build_deps = [
            "debhelper-compat (= 13)",
            "dh-sequence-golang",
            "golang-any",
        ]
        missing_deps = []
        missing_test_deps = []

        try:
            pkg = session.go_get(go_package)
            go_deps = pkg.find_dependencies()
            for dep, _ in go_deps:
                if (result := debian_packages.library_is_packaged(dep)) and (
                    debian_package := result[0]
                ):
                    debian_deps.append(debian_package)
                    if result[2] != "unstable":
                        if not quiet:
                            click.secho(
                                f"Dependency {dep} ({debian_package}) is in {result[2]}.",
                                fg="bright_blue",
                            )
                else:
                    missing_deps.append(dep)

            debian_build_deps += debian_deps

            go_test_deps = pkg.find_dependencies(True)
            for dep, _ in go_test_deps:
                if dep in go_deps:
                    continue
                elif (result := debian_packages.library_is_packaged(dep)) and (
                    debian_package := result[0]
                ):
                    if debian_package not in debian_deps:
                        debian_build_deps.append(debian_package + " <!nocheck>")
                        if result[2] != "unstable":
                            if not quiet:
                                click.secho(
                                    f"Test dependency {dep} ({debian_package}) is in {result[2]}.",
                                    fg="bright_blue",
                                )
                    else:
                        continue
                else:
                    missing_test_deps.append(dep)
        except Exception as e:
            click.echo(e)
            click.secho(
                "An error occurred fetching dependencies, skipping...", fg="red"
            )

        if not quiet:
            for dep in missing_deps:
                click.secho(f"Dependency {dep} is not packaged in Debian!", fg="yellow")
            for dep in missing_test_deps:
                click.secho(
                    f"Test dependency {dep} is not packaged in Debian!",
                    fg="yellow",
                )

        short_desc = get_short_desc(requests_session, git_repo)

        if (work_dir / "go.mod").exists():
            PACKAGE_NAME_REGEX = re.compile(r"^module (\S+)$")
            match = PACKAGE_NAME_REGEX.search((work_dir / "go.mod").read_text())
            if match:
                go_import_path = match.group(1)
            else:
                go_import_path = go_package
        else:
            go_import_path = go_package

        (work_dir / "debian").mkdir()

        header_template = env.get_template("control/header.jinja")

        control_stanzas = []

        control_stanzas.append(
            header_template.render(
                source=debian_source_package,
                uploaders=f"{name} <{email}>",
                lib_deps=debian_build_deps,
                homepage=git_repo,
                go_import_path=go_import_path,
            ).strip()
        )

        match package_type:
            case PackageType.LIBRARY:
                control_stanzas.append(
                    render_library_package(
                        env, debian_library_package, short_desc, debian_deps
                    )
                )
            case PackageType.BINARY:
                for binary in selected_binaries:
                    control_stanzas.append(
                        render_binary_package(env, binary.split("/")[-1], short_desc)
                    )
            case PackageType.LIBRARY_AND_BINARY:
                control_stanzas.append(
                    render_library_package(
                        env, debian_library_package, short_desc, debian_deps
                    )
                )
                for binary in selected_binaries:
                    control_stanzas.append(
                        render_binary_package(env, binary.split("/")[-1], short_desc)
                    )
            case PackageType.BINARY_AND_LIBRARY:
                for binary in selected_binaries:
                    control_stanzas.append(
                        render_binary_package(env, binary.split("/")[-1], short_desc)
                    )
                control_stanzas.append(
                    render_library_package(
                        env, debian_library_package, short_desc, debian_deps
                    )
                )

        control = "\n\n".join(control_stanzas) + "\n"

        with (work_dir / "debian/control").open("x") as file:
            file.write(control)

        copyr_template = env.get_template("copyright.jinja")

        with (work_dir / "debian/copyright").open("x") as file:
            file.write(
                copyr_template.render(
                    license="TODO",
                    license_text=(
                        " TODO: You can use 'gophian license-text <LICENSE>' to print a license text\n"
                        "       stanza suitable for inclusion in this DEP-5 copyright file."
                    ),
                    year=datetime.now().strftime("%Y"),
                    name=name,
                    email=email,
                    homepage=git_repo,
                    upstream_name=go_package.split("/")[-1],
                    repack=repack,
                )
            )

        changelog_template = env.get_template("changelog.jinja")

        with (work_dir / "debian/changelog").open("x") as file:
            file.write(
                changelog_template.render(
                    source=debian_source_package,
                    debian_version=version + "-1",
                    bug=f"#{wnpp.id}" if wnpp is not None else "TODO",
                    name=name,
                    email=email,
                    date=datetime.now().strftime("%a, %d %b %Y %H:%M:%S")
                    + " "
                    + datetime.utcnow().astimezone().strftime("%z")[:5],
                )
            )

        with (work_dir / "debian/.gitignore").open("x") as file:
            file.write(
                "*.debhelper\n"
                "*.log\n"
                "*.substvars\n"
                "/.debhelper/\n"
                "/build/\n"
                "/debhelper-build-stamp/\n"
                "/files\n"
            )
            if package_type.has_binary():
                for binary in selected_binaries:
                    file.write("/" + binary.split("/")[-1] + "/\n")
            if package_type.has_library():
                file.write("/" + debian_library_package + "/\n")

        with (work_dir / "debian/rules").open("x") as file:
            file.write(
                "#!/usr/bin/make -f\n"
                "\n"
                "%:\n"
                "\tdh $@ --builddirectory=debian/build --buildsystem=golang\n"
            )
            if package_type == PackageType.BINARY:
                file.write(
                    "\n"
                    "override_dh_auto_install:\n"
                    "\tdh_auto_install -- --no-source\n"
                )

        (work_dir / "debian/rules").chmod(0o0755)

        (work_dir / "debian/source").mkdir()

        with (work_dir / "debian/source/format").open("x") as file:
            file.write("3.0 (quilt)\n")

        gbp_template = env.get_template("gbp.jinja")

        with (work_dir / "debian/gbp.conf").open("x") as file:
            file.write(
                gbp_template.render(
                    pristine_tar=pristine_tar, upstream_git_history=upstream_git_history
                )
            )

        if (
            package_type == PackageType.BINARY_AND_LIBRARY
            or package_type == PackageType.LIBRARY_AND_BINARY
        ):
            with (work_dir / f"debian/{debian_library_package}.install").open(
                "x"
            ) as file:
                file.write("usr/share\n")
            if len(selected_binaries) > 1:
                for binary in selected_binaries:
                    with (work_dir / f"debian/{binary.split('/')[-1]}.install").open(
                        "x"
                    ) as file:
                        file.write(f"usr/bin/{binary.split('/')[-1]}\n")
            else:
                with (
                    work_dir / f"debian/{selected_binaries[0].split('/')[-1]}.install"
                ).open("x") as file:
                    file.write("usr/bin\n")

        move(watchfile, work_dir / "debian/watch")

        (work_dir / "debian/upstream").mkdir()

        with (work_dir / "debian/upstream/metadata").open("x") as file:
            file.write(gen_debian_upstream_metadata(git_repo))

        salsa_ci_template = env.get_template("salsa-ci.jinja")

        with (work_dir / "debian/salsa-ci.yml").open("x") as file:
            file.write(salsa_ci_template.render())

        if itp:
            itp_template = env.get_template("itp.jinja")
            itp_path = Path("itp.txt")
            if itp_path.exists():
                if not quiet:
                    click.secho(
                        "Could not generate an ITP template as itp.txt already exists. Skipping..."
                    )
            else:
                with itp_path.open("x") as file:
                    file.write(
                        itp_template.render(
                            name=name,
                            email=email,
                            package=debian_source_package,
                            short_desc=short_desc,
                            long_desc=(
                                "TODO: long description, what you'd write in debian/control\n"
                                "      keep it indented, just like in the control file"
                            ),
                            version=version,
                            upstream="TODO",
                            url=git_repo,
                            license="TODO",
                        )
                    )

        if todo:
            with (work_dir / "debian/TODO").open("x") as file:
                file.write(
                    "\n".join(
                        [
                            debian_source_package,
                            "=" * len(debian_source_package),
                            "",
                            "Resolve all the TODOs in debian/, find them using:",
                            "    grep -r TODO debian/",
                            "\n",
                        ]
                    )
                )
                if len(missing_deps) > 0:
                    file.write(
                        "There are some missing dependencies not packaged for Debian yet:\n"
                    )
                    for dep in missing_deps:
                        file.write(" - " + dep + "\n")
                    file.write("\n")
                if len(missing_test_deps) > 0:
                    file.write(
                        "There are some missing test dependencies not packaged for Debian yet:\n"
                    )
                    for dep in missing_test_deps:
                        file.write(" - " + dep + "\n")
                    file.write("\n")
                file.write(
                    "\n".join(
                        [
                            "To build the package, its recommended to use sbuild.",
                            "See https://wiki.debian.org/sbuild#Setup for setup instructions.",
                            "",
                            "When you finish packaging, delete debian/TODO:",
                            "    rm debian/TODO",
                            "and commit your changes:",
                            "    git add debian && git commit -S -m 'Initial packaging'",
                            "",
                            "To create the packaging repo on Salsa, use:",
                            "    gophian create-salsa-repo " + debian_source_package,
                            "",
                            "Once you are happy with your work, push to Salsa:",
                            "    git push --all --follow-tags",
                            "    # or",
                            "    gbp push",
                            "",
                            "If you need any help, feel free to send a mail to debian-go@lists.debian.org or",
                            "send a message on the #debian-golang IRC channel on the OFTC network.",
                        ]
                    )
                )

        if quiet:
            return

        click.secho(
            "Packaging prepared successfully in " + str(work_dir),
            bold=True,
            fg="green",
        )
        click.echo("")
        if todo:
            click.secho(
                "\n".join(
                    [
                        "A debian/TODO file has been generated for you containing the same information",
                        "here as well as any dependencies that have not been packaged in Debian.",
                    ]
                ),
                fg="blue",
            )
            click.echo("")
        click.echo("Resolve all the TODOs in debian/, find them using:")
        click.echo("    grep -r TODO debian/")
        click.echo("")
        click.echo("To build the package, its recommended to use sbuild.")
        click.echo("See https://wiki.debian.org/sbuild#Setup for setup instructions.")
        click.echo("")
        if todo:
            click.echo("When you finish packaging, delete debian/TODO:")
            click.echo("    rm debian/TODO")
            click.echo("and commit your changes:")
            click.echo("    git add debian && git commit -S -m 'Initial packaging'")
        else:
            click.echo("When you finish packaging, commit your changes:")
            click.echo("    git add debian && git commit -S -m 'Initial packaging'")
        click.echo("")
        click.echo("To create the packaging repo on Salsa, use:")
        click.echo("    gophian create-salsa-repo " + debian_source_package)
        click.echo("")
        click.echo("Once you are happy with your work, push to Salsa:")
        click.echo("    git push --all --follow-tags")
        click.echo("    # or")
        click.echo("    gbp push")
        click.echo("")
        click.echo(
            "If you need any help, feel free to send a mail to debian-go@lists.debian.org or"
        )
        click.echo(
            "send a message on the #debian-golang IRC channel on the OFTC network."
        )
        click.echo("")

        # Print experimental warning again, in case the logs drowned it out the first time.
        click.secho("gophian is experimental software!", bold=True, fg="yellow")
        click.secho("Please report any problems to:", fg="yellow")
        click.secho(BUGS_URL, fg="yellow")


def gen_debian_watch(env: Environment, repo: str, versioned: bool, repack: bool) -> str:
    url = urlparse(repo)
    if versioned:
        if url.hostname == "github.com":
            template = env.get_template("watch/github.jinja")
            return template.render(repo=repo, repack=repack)
        template = env.get_template("watch/git_tags.jinja")
        return template.render(repo=repo, repack=repack)
    else:
        template = env.get_template("watch/git_commits.jinja")
        return template.render(repo=repo, repack=repack)


def gen_debian_upstream_metadata(repo: str) -> str:
    url = urlparse(repo)
    if url.hostname == "github.com" or url.hostname == "codeberg.org":
        if url_exists(repo + "/issues"):
            return (
                "Bug-Database: " + repo + "/issues\n"
                "Bug-Submit: " + repo + "/issues/new\n"
                "Repository: " + repo + ".git\n"
                "Repository-Browse: " + repo + "\n"
            )
        else:
            return "Repository: " + repo + ".git\n" "Repository-Browse: " + repo + "\n"
    if url.hostname == "gitlab.com" or url.hostname == "salsa.debian.org":
        if url_exists(repo + "/-/issues"):
            return (
                "Bug-Database: " + repo + "/-/issues\n"
                "Bug-Submit: " + repo + "/-/issues/new\n"
                "Repository: " + repo + ".git\n"
                "Repository-Browse: " + repo + "\n"
            )
        else:
            return "Repository: " + repo + ".git\n" "Repository-Browse: " + repo + "\n"
    return (
        "Bug-Database: TODO\n"
        "Bug-Submit: TODO\n"
        "Repository: " + repo + ".git\n"
        "Repository-Browse: " + repo + "\n"
    )


def url_exists(url: str) -> bool:
    res = requests.head(url, timeout=60)
    return res.status_code == 200


def render_library_package(
    env: Environment, package: str, short_desc: str, lib_deps: List[str]
) -> str:
    library_template = env.get_template("control/go_library.jinja")
    return library_template.render(
        package=package,
        lib_deps=lib_deps,
        short_desc=short_desc,
    ).strip()


def render_binary_package(env: Environment, package: str, short_desc: str) -> str:
    binary_template = env.get_template("control/go_binary.jinja")
    return binary_template.render(
        package=package,
        short_desc=short_desc,
    ).strip()


class PackageType(Enum):
    LIBRARY = auto()
    BINARY = auto()
    LIBRARY_AND_BINARY = auto()
    BINARY_AND_LIBRARY = auto()

    @classmethod
    def from_opt(cls, opt: str):
        match opt:
            case "l":
                return cls.LIBRARY
            case "b":
                return cls.BINARY
            case "l+b":
                return cls.LIBRARY_AND_BINARY
            case "b+l":
                return cls.BINARY_AND_LIBRARY
            case _:
                return cls.LIBRARY

    def has_library(self) -> bool:
        return (
            self == self.__class__.LIBRARY
            or self == self.__class__.LIBRARY_AND_BINARY
            or self == self.__class__.BINARY_AND_LIBRARY
        )

    def has_binary(self) -> bool:
        return (
            self == self.__class__.BINARY
            or self == self.__class__.LIBRARY_AND_BINARY
            or self == self.__class__.BINARY_AND_LIBRARY
        )


class MakeSession(Session):
    def find_main_modules(self, go_package: str) -> List[str]:
        self.go_get(go_package)

        repo_path = self.gopath / "src" / go_package

        PACKAGE_REGEX = re.compile(r"^package main$")

        main_modules: List[str] = []

        for path in repo_path.rglob("*.go"):
            with path.open() as file:
                for line in file:
                    if line.strip() == "":
                        continue
                    elif match := PACKAGE_REGEX.match(line):
                        print(path, match)
                        module = path.parent.relative_to(repo_path)
                        if str(module) not in main_modules:
                            main_modules.append(str(module))
                    else:
                        break
        return main_modules

    def prepare_upstream(
        self, env: Environment, go_package: str, source_package: str
    ) -> Tuple[str, bool, Path, Path, GoPackage]:
        package = self.go_get(go_package)
        watch_path = self.tempdir.path / f"{source_package}_watch"

        repack = (package.path / "vendor").exists()

        version = package.debianized_version
        if repack:
            version += "+ds"

        with watch_path.open("x") as file:
            file.write(gen_debian_watch(env, package.repo, package.versioned, repack))

        uscan_args = [
            "uscan",
            "-dd",
            "--dehs",
            "--rename",
            "--destdir",
            ".",
            "--watchfile",
            watch_path,
            "--package",
            source_package,
            "--upstream-version",
            "0~~",
        ]

        if repack:
            copyright_path = self.tempdir.path / f"{source_package}_copyright"
            with copyright_path.open("x") as file:
                file.write(
                    "Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/\n"
                    "Files-Excluded: vendor"
                )
            uscan_args += ["--copyright-file", copyright_path]

        out = subprocess.run(
            uscan_args,
            capture_output=True,
            cwd=self.tempdir.path,
        )
        if out.returncode != 0:
            raise UscanExecutionError(out)

        xml_root = ET.fromstring(out.stdout.decode())
        target_path = cast(str, cast(ET.Element, xml_root.find("target-path")).text)

        tarball_path = self.tempdir.path / Path(target_path)

        return (version, repack, watch_path, tarball_path, package)
