#! /usr/bin/python3
# Copyright (c) TurnKey GNU/Linux - https://www.turnkeylinux.org
#
# This file is part of DeckDebuild
#
# DeckDebuild is free software; you can redistribute it and/or modify it
# under the terms of the GNU Affero General Public License as published by the
# Free Software Foundation; either version 3 of the License, or (at your
# option) any later version.

import argparse
import dataclasses
import os
import sys
from dataclasses import dataclass
from os import environ
from typing import NoReturn

from libdeckdebuild import DeckDebuildError, deckdebuild

CONF_FILE: str = "/etc/deckdebuild/deckdebuild.conf"


def fatal(msg: str | DeckDebuildError) -> NoReturn:
    print(f"error: {msg}", file=sys.stderr)
    sys.exit(1)


def warning(msg: str) -> None:
    print(f"warning: {msg}", file=sys.stderr)


@dataclass
class Config:
    root_cmd: str = "fakeroot"
    user: str = "build"
    preserve_build: str = "on-error"
    faketime: bool = True
    satisfydepends_cmd: str = "/usr/lib/pbuilder/pbuilder-satisfydepends"
    vardir: str = "/var/lib/deckdebuilds"
    build_source: bool = False
    mount_proc: bool = False
    path_to_buildroot: str = ""
    path_to_output_dir: str = os.getcwd()

    def __post_init__(self) -> None:
        self.read_conf_file()
        self.read_env()

    def _set_conf(self, key: str, value: str) -> None:
        real_value: str | bool
        key.replace("-", "_")
        if key in dataclasses.asdict(self):
            if value.lower() == "true":
                real_value = True
            elif value.lower() == "false":
                real_value = False
            else:
                real_value = value
            setattr(self, key, real_value)
            return
        warning(f"unrecognized config key '{key}' (value: {value}); ignoring")

    def read_conf_file(self, conf_file: str = "") -> None:
        if not conf_file:
            conf_file = CONF_FILE
        with open(conf_file) as fob:
            for line in fob:
                line = line.strip()
                if not line:
                    continue
                if "#" in line:
                    line = line.split("#")[0].strip()
                    if not line:
                        continue
                if "=" not in line:
                    fatal(f"invalid config line ('=' not found): '{line}'")
                key, value = line.strip().split("=", 1)
                key = key.replace("-", "_")
                self._set_conf(key.strip(), value.strip())

    def read_env(self, prefix: str = "DECKDEBUILD_") -> None:
        for environ_key in environ.keys():
            if environ_key.startswith(prefix):
                key = environ_key[len(prefix) :].lower()
                self._set_conf(key, environ[environ_key])


def arg_action(value: bool) -> str:
    bool_str = str(not value).lower()
    return f"store_{bool_str}"


def main() -> None:
    conf = Config()

    parser = argparse.ArgumentParser(
        description="build a Debian package in a decked chroot"
    )
    parser.add_argument(
        "-r",
        "--root-cmd",
        help=f"command used to gain root_privileges - default: {conf.root_cmd}"
        f" (env: DECKDEBUILD_ROOT_CMD=fakeroot)",
        default=conf.root_cmd,
    )
    parser.add_argument(
        "-u",
        "--user",
        help=f"build username (created if it doesn't exist) - default:"
        f" {conf.user} (env: DECKDEBUILD_USER=build)",
        default=conf.user,
    )
    parser.add_argument(
        "-p",
        "--preserve-buildroot",
        nargs='?',
        choices = ["always", "never", "on-error"],
        help="whether to preserve buildroot after "
        f"build - default: {conf.preserve_build}"
        f" (env: DECKDEBUILD_PRESERVE_BUILD=on-error)",
        default=conf.preserve_build
    )
    faketime_args = parser.add_mutually_exclusive_group()
    faketime_args.add_argument(
        "-f",
        "--faketime",
        help=f"use faketime (must be installed) - default: {conf.faketime}"
        f" (env: DECKDEBUILD_FAKETIME=true)",
        action=arg_action(conf.faketime),
    )
    faketime_args.add_argument(
        "-N",
        "--no-faketime",
        help=f"do not use faketime - default: {not conf.faketime}"
        f" (env: DECKDEBUILD_FAKETIME=false)",
        action=arg_action(conf.faketime),
    )
    parser.add_argument(
        "--satisfydepends-cmd",
        help="program used to satisfy build dependencies - default:"
        f"{conf.satisfydepends_cmd} (env:"
        f" DECKDEBUILD_SATISFYDEPENDS_CMD="
        "/usr/lib/pbuilder/pbuilder-satisfydepends-classic)",
        default=conf.satisfydepends_cmd,
    )
    parser.add_argument(
        "--vardir",
        help=f"var data path - default: {conf.vardir} (env:"
        f" DECKDEBUILD_VARDIR=/path/to/var_dir)",
        default=conf.vardir,
    )
    parser.add_argument(
        "-b",
        "--build-source",
        help=f"Build source package too default: {conf.build_source} (env:"
        " DECKDEBUILD_BUILD_SOURCE=true)",
        action=arg_action(conf.build_source),
    )
    parser.add_argument(
        "-P",
        "--mount-proc",
        help=f"Mount /proc in buildroot - default: {conf.mount_proc} (env:"
        " DECKDEBUILD_MOUNT_PROC=true)",
        action=arg_action(conf.mount_proc),
    )
    parser.add_argument(
        "path_to_buildroot", help="Path to an exisiting buildroot"
    )
    parser.add_argument(
        "path_to_output_dir",
        nargs="?",
        help="Path to output - default: current directory",
        default=os.getcwd(),
    )
    args = parser.parse_args()
    try:
        deckdebuild(
            os.getcwd(),
            args.path_to_buildroot,
            args.path_to_output_dir,
            root_cmd=args.root_cmd,
            user=args.user,
            preserve_build=args.preserve_buildroot,
            faketime=args.faketime,
            satisfydepends_cmd=args.satisfydepends_cmd,
            vardir=args.vardir,
            build_source=args.build_source,
            mount_proc=args.mount_proc,
        )
    except DeckDebuildError as e:
        fatal(e)


if __name__ == "__main__":
    main()
