#!/usr/bin/env python3

import os, sys
from subprocess import run
from getpass import getpass
import re
from pathlib import Path
import urllib.request
import json

CHEZMOI_GIT_TEMPLATE = 'ssh://git@git.%s:222/merlin/chezmoi.git'
GIT_SIGNUP_BASE_TEMPLATE = 'https://git-anmeldung.%s'
FLATHUB_URL = 'https://dl.flathub.org/repo/flathub.flatpakrepo'
SSH_DIRECTORY = Path('~/.ssh').expanduser()
SSH_PUBKEY_REGEX = re.compile(r'id_.*\.pub')
CHEZMOI_DIRECTORY = Path('~/.local/share/chezmoi').expanduser()

packages = [
    'git', 'python3', 'base-devel',
	'zsh', 'zsh-autosuggestions', 'zsh-completions', 'zsh-syntax-highlighting',
    'pipewire', 'pipewire-alsa', 'pipewire-pulse', 'wireplumber', 'pulsemixer', 'pavucontrol',
    'neovim', 'tree-sitter', 'tree-sitter-cli',
    'uv', 'rustup', 'ghc', 'jdk-openjdk',
    'unp',
    'kitty', 'tmux',
    'hyprland', 'uwsm', 'swww', 'rofi-wayland',
    'xdg-desktop-portal-gtk', 'xdg-desktop-portal-hyprland',
    'rsync', 'sshfs',
    'strace', 'gdb', 'man-db', 'man-pages', 'mold',
    'cmake', 'meson', 'cpio', 'pkg-config',
    'sleuthkit', 'ghidra',
    'shellcheck',
    'sddm',
    'reflector',
    'yazi', 'ncdu', 'bat', 'htop', 'btop', 'chezmoi', 'fzf', 'jq', 'ripgrep',
    'efibootmgr',
    'firewalld',
    'flatpak',
    'hyprland', 'hyprlang',
    'nushell',
    'openvpn',
    'os-prober',
    'rbw',
    'powertop',
    'wget', 'curl', 'wireshark-qt',
    'podman', 'podman-compose',
    'plocate',
    'cosmic-player',
    'gimp',
    'texlive',
    'blueman',
    'signal-desktop',
    'otf-fantasque-sans-mono', 'ttf-fantasque-sans-mono',
]

# Not currently installed due to security concerns
aur = [
    'podlet', 'icdiff', 'choosenim', 'imhex',
]

flatpaks = {
    'Flatseal': 'com.github.tchx84.Flatseal',
    'Easy Effects': 'com.github.wwmm.easyeffects',
    'draw.io': 'com.jgraph.drawio.desktop',
    'TIDAL Hi-Fi': 'com.mastermindzh.tidal-hifi',
    'Bottles': 'com.usebottles.bottles',
    'Ungoogled Chromium': 'io.github.ungoogled_software.ungoogled_chromium',
    'LibreWolf': 'io.gitlab.librewolf-community',
    'Firefox': 'org.mozilla.firefox',
    'OnlyOffice': 'org.onlyoffice.desktopeditors',
    'Evolution': 'org.gnome.Evolution',
}

class TooManySshPubKeysException(Exception):
    pass

class FlatpakAddFlathubException(Exception):
    pass

class FlatpakInstallException(Exception):
    pass

class PacmanInstallException(Exception):
    pass

class PacmanUpdateException(Exception):
    pass

class ChezmoiException(Exception):
    pass

def post_json(url: str, data) -> (int, str):
    json_data = json.dumps(data).encode("utf-8")

    headers = {"Content-Type": "application/json"}

    req = urllib.request.Request(url, data=json_data, headers=headers, method='POST')

    with urllib.request.urlopen(req) as response:
        status_code = response.getcode()
        response_body = response.read().decode('utf-8')
        return status_code, response_body

def assert_flathub():
    done = False
    try:
        done = assert_flathub.done
    except AttributeError:
        pass
    if not done:
        completed = run(['flatpak', 'remote-add', '--if-not-exists', '--user', 'flathub', FLATHUB_URL])
        assert_flathub.done = completed.returncode == 0
        if not assert_flathub.done:
            raise FlatpakAddFlathubException(f'remote-add failed with exit code: {completed.returncode}')

def install_flatpaks(packages: dict[str, str] | list[str]):
    assert_flathub()
    exact_packages: list[str] = packages if isinstance(packages, list) else list(packages.values())
    ret = run(['flatpak', 'install', '--noninteractive', '--assumeyes', '--user'] + exact_packages).returncode
    if ret != 0:
        raise FlatpakInstallException(f"Failed to install the specified flatpaks. Exit code: {ret}")

def install_packages(packages: list[str]):
    if os.geteuid() != 0:
        print("You are not root. Need root password to install system packages.")
        ret = run(['su', '-c', f'python3 {sys.argv[0]} --pacman-only']).returncode
        if ret != 0:
            raise PacmanInstallException(f'Failed to install pacman packages as root. Exit code: {ret}')
        return
    ret = run(['pacman', '-Sy', '--noconfirm']).returncode
    if ret != 0:
        raise PacmanUpdateException(f'Failed to update pacman repos. Exit code: {ret}')
    ret = run(['pacman', '-S', '--needed', '--noconfirm'] + packages).returncode
    if ret != 0:
        raise PacmanInstallException(f'Failed to install the specified packages. Exit code: {ret}')

def get_ssh_pubkey():
    matches: list[Path] = []
    if SSH_DIRECTORY.exists():
        for path in SSH_DIRECTORY.iterdir():
            if SSH_PUBKEY_REGEX.fullmatch(path.name):
                matches.append(path)

        if len(matches) > 1:
            raise TooManySshPubKeysException(f"There are {len(matches)} ssh public keys.")
        if len(matches) == 1:
            print("Found an ssh public key")
            return matches[0]
    else:
        print("Directory does not yet exist:", SSH_DIRECTORY)
    print("You need to generate an ssh keypair.")
    ret = run(['ssh-keygen']).returncode
    if ret != 0:
        raise SshKeygenException(f'ssh-keygen failed with exit code: {ret}')
    return get_ssh_pubkey()

def register_machine(machine_name: str | None = None, auth_pw: str | None = None, password: str | None = None):
    pubkey_path = get_ssh_pubkey()
    should_register = False
    print("Registering the machine with Gitea...")
    pubkey = pubkey_path.read_text()
    if not auth_pw:
        auth_pw = getpass("What gives you the right to do that?").strip()
    if not machine_name:
        machine_name = input("Input the gitea username:").strip()
    if not password:
        password = getpass("Set your password:").strip()
    status_code, response = post_json(GIT_SIGNUP_BASE + '/new-machine', data = {
        'name': machine_name,
        'public_ssh_key': pubkey,
        'password': password,
        'auth_pw': auth_pw,
    })
    if status_code != 200:
        raise MachineRegistrationException(f'Request to add new machine resultet in status code {status_code} and response: {response}')
    print(response)

def initialize_chezmoi():
    if CHEZMOI_DIRECTORY.exists() and (CHEZMOI_DIRECTORY / '.git').exists():
        return
    print("Initializing chezmoi...")
    ret = run(['chezmoi', 'init', CHEZMOI_GIT]).returncode
    if ret != 0:
        raise ChezmoiException(f"Failed to initialize chezmoi. Exit code: {ret}")

def apply_chezmoi():
    initialize_chezmoi()
    print("Applying chezmoi...")
    ret = run(['chezmoi', 'apply', '--force']).returncode
    if ret != 0:
        raise ChezmoiException(f'Failed to apply chezmoi. Exit code: {ret}')

def configure_display_manager():
    print("Configuring display manager")
    root_commands = [
        'systemctl disable display-manager.service',
        'systemctl enable sddm.service'
    ]
    run(['su', '-c', ' ; '.join(root_commands)])

def configure():
    print("Configuring...")
    apply_chezmoi()
    configure_display_manager()


def main(args):
    global CHEZMOI_GIT, GIT_SIGNUP_BASE
    import argparse

    parser = argparse.ArgumentParser(description="Script for installing packages.")

    parser.add_argument(
        '--pacman-only',
        action='store_true',
        default=False,
        help="Only installs pacman packages. No flatpaks."
    )

    parser.add_argument(
        '--skip-gitea-registration',
        action='store_true',
        default=False,
        help='Skip Gitea registration.'
    )

    parser.add_argument(
        '--no-skip-configure',
        action='store_false',
        default=True,
        help='Do not skip configuring (with chezmoi) even when registration is skipped.'
    )

    parser.add_argument(
        'base-domain',
        type=str,
        help='The base domain used for the install.'
    )

    args = parser.parse_args()

    CHEZMOI_GIT = CHEZMOI_GIT_TEMPLATE % args.base_domain
    GIT_SIGNUP_BASE = GIT_SIGNUP_BASE_TEMPLATE % args.base_domain

    install_packages(packages)

    if args.pacman_only:
        return

    install_flatpaks(flatpaks)

    if not args.skip_gitea_registration:
        register_machine()

    if args.no_skip_configure:
        configure()


if __name__ == '__main__':
    main(sys.argv)
