import subprocess
import sys
import os
import platform
import hashlib
import click
import shutil
import toml
import hashlib
import glob
import urllib.request
from dataclasses import dataclass

PACKAGER_TARGET_DIR = "target/packager"

@dataclass
class PackageInfo:
    name: str
    version: str
    description: str
    publisher: str
    url: str
    modulo_version: str

@click.group()
def cli():
    pass

@cli.command()
@click.option('--skipcargo', default=False, is_flag=True, help="Skip cargo release build")
def build(skipcargo):
    """Build espanso distribution"""
    # Check operating system
    TARGET_OS = "macos"
    if platform.system() == "Windows":
        TARGET_OS = "windows"
    elif platform.system() == "Linux":
        TARGET_OS = "linux"

    print("Detected OS:", TARGET_OS)

    print("Loading info from Cargo.toml")
    cargo_info = toml.load("Cargo.toml")
    package_info = PackageInfo(cargo_info["package"]["name"],
                               cargo_info["package"]["version"],
                               cargo_info["package"]["description"],
                               cargo_info["package"]["authors"][0],
                               cargo_info["package"]["homepage"],
                               cargo_info["modulo"]["version"])
    print(package_info)

    if not skipcargo:
        print("Building release version...")
        subprocess.run(["cargo", "build", "--release"])
    else:
        print("Skipping build")

    if TARGET_OS == "windows":
        build_windows(package_info)
    elif TARGET_OS == "macos":
        build_mac(package_info)

def calculate_sha256(file):
    with open(file, "rb") as f:
        b = f.read() # read entire file as bytes
        readable_hash = hashlib.sha256(b).hexdigest()
        return readable_hash

def build_windows(package_info):
    print("Starting packaging process for Windows...")

    # Check Inno Setup
    try:
        subprocess.run(["iscc"], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
    except FileNotFoundError:
        raise Exception("Could not find Inno Setup compiler. Please install it from here: http://www.jrsoftware.org/isdl.php")

    print("Clearing target dirs")

    # Clearing previous build directory
    if os.path.isdir(PACKAGER_TARGET_DIR):
        print("Cleaning packager temp directory...")
        shutil.rmtree(PACKAGER_TARGET_DIR)

    TARGET_DIR = os.path.join(PACKAGER_TARGET_DIR, "win")
    os.makedirs(TARGET_DIR, exist_ok=True)

    modulo_url = "https://github.com/federico-terzi/modulo/releases/download/v{0}/modulo-win.exe".format(package_info.modulo_version)
    modulo_sha_url = "https://github.com/federico-terzi/modulo/releases/download/v{0}/modulo-win.exe.sha256.txt".format(package_info.modulo_version)
    print("Pulling modulo depencency from:", modulo_url)
    modulo_target_file = os.path.join(TARGET_DIR, "modulo.exe")
    urllib.request.urlretrieve(modulo_url, modulo_target_file)
    print("Pulling SHA signature from:", modulo_sha_url)
    modulo_sha_file = os.path.join(TARGET_DIR, "modulo.sha256")
    urllib.request.urlretrieve(modulo_sha_url, modulo_sha_file)
    print("Checking signatures...")
    expected_sha = None
    with open(modulo_sha_file, "r") as sha_f:
        expected_sha = sha_f.read()
    actual_sha = calculate_sha256(modulo_target_file)
    if actual_sha != expected_sha:
        raise Exception("Modulo SHA256 is not matching")

    print("Gathering CRT DLLs...")
    msvc_dirs = glob.glob("C:\\Program Files (x86)\\Microsoft Visual Studio\\2019\\*\\VC\\Redist\\MSVC\\*")
    print("Found Redists: ", msvc_dirs)

    print("Determining best redist...")

    if len(msvc_dirs) == 0:
        raise Exception("Cannot find redistributable dlls")

    msvc_dir = None

    for curr_dir in msvc_dirs:
        dll_files = glob.glob(curr_dir + "\\x64\\*CRT\\*.dll")
        print("Found dlls", dll_files, "in", curr_dir)
        if any("vcruntime140_1.dll" in x.lower() for x in dll_files):
            msvc_dir = curr_dir
            break

    if msvc_dir is None:
        raise Exception("Cannot find redist with VCRUNTIME140_1.dll")

    print("Using: ", msvc_dir)

    dll_files = glob.glob(msvc_dir + "\\x64\\*CRT\\*.dll")

    print("Found DLLs:")
    include_list = []
    for dll in dll_files:
        print("Including: "+dll)
        include_list.append("Source: \""+dll+"\"; DestDir: \"{app}\"; Flags: ignoreversion")

    print("Including modulo")
    include_list.append("Source: \""+os.path.abspath(modulo_target_file)+"\"; DestDir: \"{app}\"; Flags: ignoreversion")

    include = "\r\n".join(include_list)

    INSTALLER_NAME = f"espanso-win-installer"

    # Inno setup
    shutil.copy("packager/win/modpath.iss", os.path.join(TARGET_DIR, "modpath.iss"))

    print("Processing inno setup template")
    with open("packager/win/setupscript.iss", "r") as iss_script:
        content = iss_script.read()

        # Replace variables
        content = content.replace("{{{app_name}}}", package_info.name)
        content = content.replace("{{{app_version}}}", package_info.version)
        content = content.replace("{{{app_publisher}}}", package_info.publisher)
        content = content.replace("{{{app_url}}}", package_info.url)
        content = content.replace("{{{app_license}}}",  os.path.abspath("LICENSE"))
        content = content.replace("{{{app_icon}}}",  os.path.abspath("packager/win/icon.ico"))
        content = content.replace("{{{executable_path}}}",  os.path.abspath("target/release/espanso.exe"))
        content = content.replace("{{{output_dir}}}",  os.path.abspath(TARGET_DIR))
        content = content.replace("{{{output_name}}}",  INSTALLER_NAME)
        content = content.replace("{{{dll_include}}}",  include)

        with open(os.path.join(TARGET_DIR, "setupscript.iss"), "w") as output_script:
            output_script.write(content)

    print("Compiling installer with Inno setup")
    subprocess.run(["iscc", os.path.abspath(os.path.join(TARGET_DIR, "setupscript.iss"))])

    print("Calculating the SHA256")
    sha256_hash = hashlib.sha256()
    with open(os.path.abspath(os.path.join(TARGET_DIR, INSTALLER_NAME+".exe")),"rb") as f:
        # Read and update hash string value in blocks of 4K
        for byte_block in iter(lambda: f.read(4096),b""):
            sha256_hash.update(byte_block)

        hash_file = os.path.abspath(os.path.join(TARGET_DIR, "espanso-win-installer-sha256.txt"))
        with open(hash_file, "w") as hf:
            hf.write(sha256_hash.hexdigest())


def build_mac(package_info):
    print("Starting packaging process for MacOS...")

    print("Clearing target dirs")

    # Clearing previous build directory
    if os.path.isdir(PACKAGER_TARGET_DIR):
        print("Cleaning packager temp directory...")
        shutil.rmtree(PACKAGER_TARGET_DIR)

    TARGET_DIR = os.path.join(PACKAGER_TARGET_DIR, "mac")
    os.makedirs(TARGET_DIR, exist_ok=True)

    print("Compressing release to archive...")
    target_name = f"espanso-mac.tar.gz"
    archive_target = os.path.abspath(os.path.join(TARGET_DIR, target_name))
    subprocess.run(["tar",
                    "-C", os.path.abspath("target/release"),
                    "-cvf",
                    archive_target,
                    "espanso",
                    ])
    print(f"Created archive: {archive_target}")

    print("Calculating the SHA256")
    sha256_hash = hashlib.sha256()
    with open(archive_target,"rb") as f:
        # Read and update hash string value in blocks of 4K
        for byte_block in iter(lambda: f.read(4096),b""):
            sha256_hash.update(byte_block)

        hash_file = os.path.abspath(os.path.join(TARGET_DIR, "espanso-mac-sha256.txt"))
        with open(hash_file, "w") as hf:
            hf.write(sha256_hash.hexdigest())

    modulo_sha_url = "https://github.com/federico-terzi/modulo/releases/download/v{0}/modulo-mac.sha256.txt".format(package_info.modulo_version)
    print("Pulling SHA signature from:", modulo_sha_url)
    modulo_sha_file = os.path.join(TARGET_DIR, "modulo.sha256")
    urllib.request.urlretrieve(modulo_sha_url, modulo_sha_file)
    modulo_sha = None
    with open(modulo_sha_file, "r") as sha_f:
        modulo_sha = sha_f.read()
    if modulo_sha is None:
        raise Exception("Cannot determine modulo SHA")

    print("Processing Homebrew formula template")
    with open("packager/mac/espanso.rb", "r") as formula_template:
        content = formula_template.read()

        # Replace variables
        content = content.replace("{{{app_desc}}}", package_info.description)
        content = content.replace("{{{app_url}}}", package_info.url)
        content = content.replace("{{{app_version}}}", package_info.version)
        content = content.replace("{{{modulo_version}}}", package_info.modulo_version)
        content = content.replace("{{{modulo_sha}}}", modulo_sha)

        # Calculate hash
        with open(archive_target, "rb") as f:
            bytes = f.read()
            readable_hash = hashlib.sha256(bytes).hexdigest()
            content = content.replace("{{{release_hash}}}", readable_hash)

        with open(os.path.join(TARGET_DIR, "espanso.rb"), "w") as output_script:
            output_script.write(content)

    print("Done!")


if __name__ == '__main__':
    print("[[ espanso packager ]]")

    # Check python version 3
    if sys.version_info[0] < 3:
        raise Exception("Must be using Python 3")

    cli()