"""Configure a Gentoo guest with cloud image settings

This step keeps track of how far it's gotten, so re-running this command
will continue on if an error was to occur, unless --start-over flag is given.
"""

import os
import sys
import shutil
import configparser
from subprocess import Popen, PIPE
import logging
import traceback

import gentooimgr.config
import gentooimgr.configs
import gentooimgr.common
import gentooimgr.chroot
import gentooimgr.kernel
from gentooimgr import LOG
from gentooimgr import HERE

from gentooimgr.configs import *

FILES_DIR = os.path.join(HERE, "..")

def step1_diskprep(args, cfg):
    LOG.info("\t:: Step 1: Disk Partitioning")
    # http://rainbow.chard.org/2013/01/30/how-to-align-partitions-for-best-performance-using-parted/
    # http://honglus.blogspot.com/2013/06/script-to-automatically-partition-new.html
    cmds = [
            ['parted', '-s', f'{cfg.get("disk")}', 'mklabel', 'msdos'],
            ['parted', '-s', f'{cfg.get("disk")}', 'mkpart', 'primary', '2048s', '100%'],
            ['partprobe'],
            ['mkfs.ext4', '-FF', f'{cfg.get("disk")}{cfg.get("partition", 1)}']
    ]
    for c in cmds:
        proc = Popen(c, stdout=PIPE, stderr=PIPE)
        stdout, stderr = proc.communicate()

    completestep(1, "diskprep")

def step2_mount(args, cfg):
    LOG.info(f'\t:: Step 2: Mounting {gentooimgr.config.GENTOO_MOUNT}')
    proc = Popen(["mount", f'{cfg.get("disk")}{cfg.get("partition")}', cfg.get("mountpoint")])
    proc.communicate()
    completestep(2, "mount")

def step3_stage3(args, cfg):
    LOG.info(f'\t:: Step 3: Stage3 Tarball')

    stage3 = cfg.get("stage3") or args.stage3  # FIXME: auto detect stage3 images in mountpoint and add here
    if not stage3:
        stage3 = gentooimgr.common.stage3_from_dir(FILES_DIR)


    proc = Popen(["tar", "xpf", os.path.abspath(stage3), "--xattrs-include='*.*'", "--numeric-owner", "-C",
                  f'{cfg.get("mountpoint")}'])
    proc.communicate()
    completestep(3, "stage3")

def step4_binds(args, cfg):
    LOG.info(f'\t:: Step 4: Binding Filesystems')
    gentooimgr.chroot.bind(verbose=False)
    completestep(4, "binds")

def step5_portage(args, cfg):
    LOG.info(f'\t:: Step 5: Portage')
    portage = cfg.get("portage") or args.portage
    if not portage:
        portage = gentooimgr.common.portage_from_dir(FILES_DIR)
    proc = Popen(["tar", "xpf", portage, "-C", f"{cfg.get('mountpoint')}/usr/"])
    proc.communicate()
    # Edit portage
    portage_env = os.path.join(cfg.get("mountpoint"), 'etc', 'portage', 'env')
    os.makedirs(portage_env, exist_ok=True)
    with open(os.path.join(portage_env, 'singlejob.conf'), 'w') as f:
        f.write('MAKEOPTS="-j1"\n')

    env_path = os.path.join(cfg.get("mountpoint"), 'etc', 'portage', 'package.env')
    with open(env_path, 'w') as f:
        f.write("app-portage/eix singlejob.conf\ndev-util/maturin singlejob.conf\ndev-util/cmake singlejob.conf")

    completestep(5, "portage")

def step6_licenses(args, cfg):
    LOG.info(f'\t:: Step 6: Licenses')
    license_path = os.path.join(cfg.get("mountpoint"), 'etc', 'portage', 'package.license')
    os.makedirs(license_path, exist_ok=True)
    for f, licenses in cfg.get("licensefiles", {}).items():
        with open(os.path.join(license_path, f), 'w') as f:
            f.write('\n'.join(licenses))
    completestep(6, "license")

def step7_repos(args, cfg):
    LOG.info(f'\t:: Step 7: Repo Configuration')
    repo_path = os.path.join(cfg.get("mountpoint"), 'etc', 'portage', 'repos.conf')
    os.makedirs(repo_path, exist_ok=True)
    # Copy from template
    repo_file = os.path.join(repo_path, 'gentoo.conf')
    shutil.copyfile(
        os.path.join(cfg.get("mountpoint"), 'usr', 'share', 'portage', 'config', 'repos.conf'),
        repo_file)
    # Regex replace lines
    cp = configparser.ConfigParser()
    for repofile, data in cfg.get("repos", {}).items():
        cp.read(cfg.get("mountpoint") + repofile)  # repofile should be absolute path, do not use os.path.join.
        for section, d in data.items():
            if section in cp:
                for key, val in d.items():
                    # Replace everything after the key with contents of value.
                    # Sed is simpler than using regex for this purpose.
                    cp.set(section, key, val)
            else:
                sys.stderr.write(f"\tWW No section {section} in {repofile}\n")

        cp.write(open(cfg.get("mountpoint") + repofile, 'w'))

    completestep(7, "repos")

def step8_resolv(args, cfg):
    LOG.info(f'\t:: Step 8: Resolv')
    proc = Popen(["cp", "--dereference", "/etc/resolv.conf", os.path.join(cfg.get("mountpoint"), 'etc')])
    proc.communicate()
    # Copy all step files and python module to new chroot
    os.system(f"cp /tmp/*.step {cfg.get('mountpoint')}/tmp")
    os.system(f"cp -r . {cfg.get('mountpoint')}/mnt/")
    completestep(8, "resolv")

def step9_sync(args, cfg):
    LOG.info(f"\t:: Step 9: sync")
    LOG.info("\t\t:: Entering chroot")
    os.chroot(cfg.get("mountpoint"))
    os.chdir(os.sep)
    os.system("source /etc/profile")
    proc = Popen(["emerge", "--sync", "--quiet"])
    proc.communicate()
    LOG.info("\t\t:: Emerging base")
    proc = Popen(["emerge", "--update", "--deep", "--newuse", "--keep-going", "@world"])
    proc.communicate()
    completestep(9, "sync")

def step10_emerge_pkgs(args, cfg):
    LOG.info(f"\t:: Step 10: emerge pkgs")
    packages = cfg.get("packages", {})
    for oneshot_up in packages.get("oneshots", []):
        proc = Popen(["emerge", "--oneshot", "--update", oneshot_up])
        proc.communicate()

    for single in packages.get("singles", []):
        proc = Popen(["emerge", "-j1", single])
        proc.communicate()

    LOG.info(f"KERNEL PACKAGES {packages.get('kernel')}")
    if packages.get("kernel", []):
        cmd = ["emerge", "-j", str(args.threads)] + packages.get("kernel", [])
        proc = Popen(cmd)
        proc.communicate()

    cmd = ["emerge", "-j", str(args.threads), "--keep-going"]
    cmd += packages.get("keepgoing", [])
    proc = Popen(cmd)
    proc.communicate()

    cmd = ["emerge", "-j", str(args.threads)]
    cmd += packages.get("base", [])
    cmd += packages.get("additional", [])
    cmd += packages.get("bootloader", [])
    LOG.info(cmd)
    proc = Popen(cmd)
    proc.communicate()
    completestep(10, "pkgs")

def step11_kernel(args, cfg):
    # at this point, genkernel will be installed
    LOG.info(f"\t:: Step 11: kernel")
    proc = Popen(["eselect", "kernel", "set", "1"])
    proc.communicate()
    if not args.kernel_dist:
        os.chdir(args.kernel_dir)
        threads = str(gentooimgr.config.THREADS)
        gentooimgr.kernel.build_kernel(args, cfg)

    completestep(11, "kernel")

def step12_grub(args, cfg):
    LOG.info(f"\t:: Step 12: kernel")
    proc = Popen(["grub-install", cfg.get('disk')])
    proc.communicate()
    code = proc.returncode
    if code != 0:
        sys.stderr.write(f"Failed to install grub on {cfg.get('disk')}\n")
        sys.exit(code)

    with open("/etc/default/grub", 'w') as f:
        f.write(f"{gentooimgr.kernel.GRUB_CFG}")

    proc = Popen(["grub-mkconfig", "-o", "/boot/grub/grub.cfg"])
    proc.communicate()
    completestep(12, "grub")

def step13_serial(args, cfg):
    LOG.info(f"\t:: Step 13: Serial")
    os.system("sed -i 's/^#s0:/s0:/g' /etc/inittab")
    os.system("sed -i 's/^#s1:/s1:/g' /etc/inittab")
    completestep(13, "serial")

def step14_services(args, cfg):
    LOG.info(f"\t:: Step 14: Services")
    for service in ["acpid", "syslog-ng", "cronie", "sshd", "cloud-init-local", "cloud-init", "cloud-config",
                    "cloud-final", "ntpd", "nfsclient"]:
        if args.profile == "systemd":
            proc = Popen(["systemctl", "enable", service])
        else:
            proc = Popen(["rc-update", "add", service, "default"])
        proc.communicate()

    completestep(14, "services")

def step15_ethnaming(args, cfg):
    LOG.info(f"\t:: Step 15: Eth Naming")
    completestep(15, "networking")

def step16_sysconfig(args, cfg):
    LOG.info(f"\t:: Step 16: Sysconfig")
    with open("/etc/timezone", "w") as f:
        f.write("UTC")
    proc = Popen(["emerge", "--config", "sys-libs/timezone-data"])
    proc.communicate()
    with open("/etc/locale.gen", "a") as f:
        f.write("en_US.UTF-8 UTF-8\nen_US ISO-8859-1\n")
    proc = Popen(["locale-gen"])
    proc.communicate()
    proc = Popen(["eselect", "locale", "set", "en_US.utf8"])
    proc.communicate()
    proc = Popen(["env-update"])
    proc.communicate()
    with open('/etc/sysctl.d/swappiness.conf', 'w') as f:
        f.write("vm.swappiness = 0\n")

    modloadpath = os.path.join(os.sep, 'etc', 'modules-load.d')
    os.makedirs(modloadpath, exist_ok=True)
    with open(os.path.join(modloadpath, 'cloud-modules.conf'), 'w') as f:
        f.write('\n'.join(gentooimgr.config.CLOUD_MODULES))

    cloudcfg = os.path.join(os.sep, 'etc', 'cloud')
    if not os.path.exists(cloudcfg):
        os.makedirs(cloudcfg, exist_ok=True)
        os.makedirs(os.path.join(cloudcfg, 'templates'), exist_ok=True)
    with open(os.path.join(cloudcfg, 'cloud.cfg'), 'w') as cfg:
        cfg.write(f"{CLOUD_YAML}")

    os.chmod(os.path.join(cloudcfg, "cloud.cfg"), 0o644)

    with open(os.path.join(cloudcfg, "templates", "hosts.gentoo.tmpl"), 'w') as tmpl:
        tmpl.write(f"{HOST_TMPL}") # FIXME:

    os.chmod(os.path.join(cloudcfg, "templates", "hosts.gentoo.tmpl"), 0o644)

    proc = Popen("sed -i 's/domain_name\,\ domain_search\,\ host_name/domain_search/g' /etc/dhcpcd.conf", shell=True)
    proc.communicate()

    hostname = os.path.join(os.sep, 'etc', 'conf.d', 'hostname')
    with open(hostname, 'w') as f:
        f.write(f"{HOSTNAME}\n")

    os.chmod(hostname, 0o644)

    proc = Popen(["eix-update"])
    proc.communicate()

    os.remove(os.path.join(os.sep, 'etc', 'resolv.conf'))

    completestep(16, "sysconfig")

def step17_fstab(args, cfg):
    LOG.info(f"\t:: Step 17: fstab")
    with open(os.path.join(os.sep, 'etc', 'fstab'), 'a') as fstab:
        fstab.write(f"{cfg.get('disk')}\t/\text4\tdefaults,noatime\t0 1\n")

    completestep(17, "fstab")

def completestep(step, stepname, prefix='/tmp'):
    with open(os.path.join(prefix, f"{step}.step"), 'w') as f:
        f.write("done.")  # text in this file is not currently used.


def getlaststep(prefix='/tmp'):
    i = 1
    found = False
    while not found:
        if os.path.exists(f"{i}.step"):
            i += 1
        else:
            found = True

    return i


def stepdone(step, prefix='/tmp'):
    return os.path.exists(os.path.join(prefix, f"{step}.step"))

def configure(args, config: dict):
    # Load configuration
    if not os.path.exists(gentooimgr.config.GENTOO_MOUNT):
        if not args.force:
            # We aren't in a gentoo live cd are we?
            sys.stderr.write("Your system doesn't look like a gentoo live cd, exiting for safety.\n"
                "If you want to continue, use --force option and re-run `python -m gentooimgr install` with your configuration\n")
            sys.exit(1)

        else:
            # Assume we are root as per live cd, otherwise user should run this as root as a secondary confirmation
            os.makedirs(gentooimgr.config.GENTOO_MOUNT)
    # disk prep
    cfg = config
    # must be root for linux-6.1.52-pentoo/certs/signing_key.pem
    if not stepdone(1): step1_diskprep(args, cfg)
    # mount root
    if not stepdone(2): step2_mount(args, cfg)
    # extract stage
    if not stepdone(3): step3_stage3(args, cfg)
    # mount binds
    if not stepdone(4): step4_binds(args, cfg)
    # extract portage
    if not stepdone(5): step5_portage(args, cfg)
    # Set licenses
    if not stepdone(6): step6_licenses(args, cfg)
    # repos.conf
    if not stepdone(7): step7_repos(args, cfg)
    # portage env files and resolv.conf
    if not stepdone(8): step8_resolv(args, cfg)
    # emerge --sync
    if not stepdone(9): step9_sync(args, cfg)
    # bindist
    if not stepdone(10): step10_emerge_pkgs(args, cfg)
    # emerge packages
    # configure & emerge kernel (use cloud configuration too)
    if not stepdone(11): step11_kernel(args, cfg)
    # grub
    if not stepdone(12): step12_grub(args, cfg)
    # enable serial console
    if not stepdone(13): step13_serial(args, cfg)
    # services
    if not stepdone(14): step14_services(args, cfg)
    # eth0 naming
    # timezone
    if not stepdone(15): step15_ethnaming(args, cfg)
    # locale
    # set some sysctl things
    # set some dhcp things
    # hostname
    if not stepdone(16): step16_sysconfig(args, cfg)
    # fstab
    if not stepdone(17): step17_fstab(args, cfg)
    # copy cloud cfg?
    gentooimgr.chroot.unbind()
    # Finish install processes like emaint and eix-update and news read


