#! /usr/bin/env python3

import sys, os, argparse, json, shutil, tempfile, subprocess, traceback

def run_script(script_path: str) -> int:
    """
    Helper to handle subprocess execution
    Args:
        script_path (str): script path to execute
    Returns:
        int: 0 if both succeed, 1 if any step fails
    """
    if not os.path.exists(script_path):
        print(f"Error: Script not found at {script_path}", file=sys.stderr)
        return 1
    
    try:
        subprocess.run(f'{script_path}', check=True, capture_output=True, text=True)
        return 0
    except subprocess.CalledProcessError as e:
        print(f"Error: {os.path.basename(script_path)} failed (Exit {e.returncode})", file=sys.stderr)
        print(f"Details: {e.stderr.strip() or e.stdout.strip()}", file=sys.stderr)
        return 1
    except PermissionError:
        print(f"Error: {os.path.basename(script_path)} is not executable. Check permissions.", file=sys.stderr)
        return 1

def handle_start(src_dir, install_path):
    """
    Start application

    Args:
        src_dir (str): Path to source directory (Working directory)
        install_path (str): Path to target installation directory
    Returns:
        int: 0 -> success
    """

    try:
        # disable app
        if os.path.exists(os.path.join(install_path, 'disable')):
            subprocess.check_output(f'{install_path}/disable')

        # copy all source files to installation path
        shutil.copytree(src_dir, install_path, dirs_exist_ok=True)

        # start application
        if run_script(f'{install_path}/start') != 0:
            return 1

        return 0
    except Exception as ex:
        print(f'Error: {ex}')
        return 1

def handle_stop(install_path: str):
    """
    Stop application
    Args:
        install_path (str): Path to installation directory
    Returns:
        int: 0 -> success
    """

    if not os.path.exists(os.path.join(install_path, 'stop')):
        print(f"Error: stop script not found at {install_path}")
        return 1

    try:
        # stop application
        run_script(f'{install_path}/stop')

        return 0
    except subprocess.CalledProcessError as e:
        print(f"Error: Stop script failed with exit code {e.returncode}")
        print(f"Output: {e.stderr or e.stdout}")
        return 1

def handle_restart(src_path: str, install_path: str):
    """
    Restart application
    Args:
        install_path (str): Path to installation directory
    Returns:
        int: 0 -> success
    """
    if handle_stop(install_path) != 0:
        print("Restart aborted: Stop script execution failed.", file=sys.stderr)
        return 1

    if handle_start(src_path, install_path) != 0:
        print("Restart aborted: Start script execution failed.", file=sys.stderr)
        return 1

    print('Application restarted successfully.')
    return 0


def uninstall_app(args: argparse.Namespace, install_path) -> int:
    """
    Uninstall application at specified installation directory.

    Args:
        install_path (str): Path to target installation directory.
    Returns: 
        Int
    """

    rval = 0
    try:

        # disable application then uninstall
        disable_path = os.path.join(install_path, 'disable')
        stop_path    = os.path.join(install_path, 'stop')
        if os.path.exists(disable_path):
            subprocess.check_output(disable_path)

        if os.path.exists(stop_path):
            subprocess.check_output(stop_path)
        
        # reset vols, networks
        subprocess.check_output(['docker', 'compose', f'-f {install_path}/docker-compose.yml', 'down', '-v'])

        if os.path.exists(install_path):
            shutil.rmtree(install_path)
    except Exception as e:
        print(f'Exception encountered: {e}')
        traceback.print_exc()
        rval = 1
    return rval

# script constants
utils_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), os.pardir))
temp_dir = tempfile.mkdtemp()
build_dir = os.path.join(temp_dir, 'build')
staging_dir = os.path.join(temp_dir, 'staging')
working_dir = working_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
src_path = os.path.join(working_dir, 'src')
CONFIG_JSON = os.path.join(working_dir, 'config.json')

templates_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', 'hosted-app-utils', 'templates'))

template_files = {
    f'{templates_dir}/start': f'{working_dir}/src/start',
    f'{templates_dir}/stop': f'{working_dir}/src/stop',
    f'{templates_dir}/disable': f'{working_dir}/src/disable'
}

# command line arguments
parser = argparse.ArgumentParser(
    prog="clientappctl",
    description="Manage client application operations. [ start, stop, restart ]")

parser.add_argument("-c", "--config", default=CONFIG_JSON, help=f"Path to application configuration (default: {CONFIG_JSON})")

subparsers = parser.add_subparsers(dest="command", required=True)

start_parser = subparsers.add_parser("start", help="Start the application")
stop_parser = subparsers.add_parser("stop", help="Stop the application")
restart_parser = subparsers.add_parser("restart", help="Restart the application")

def main() -> int:
    # parse user args
    args = parser.parse_args()

    # load configuration
    try:
        config_path = args.config
        if not os.path.exists(config_path):
            raise ValueError("Configuration not found.")

        with open(config_path) as f:
            config_data = json.load(f)
            name_slug = config_data['name'].strip().replace(' ', '_').replace('-', '_').lower()
            org_slug  = config_data['org'].strip().replace(' ', '_').replace('-', '_').lower()
            version   = config_data['version']

        install_path = f'/var/signalytic/clientapps/{org_slug}/{name_slug}'

        # copy temp files into working dir
        for src, dest in template_files.items():
            if not os.path.exists(src):
                continue
            shutil.copy(src, dest)

        if args.command == "start":
            print(f"Starting app: {config_data['name']}-{version}")

            if not os.path.exists(install_path):
                os.makedirs(install_path)
            return handle_start(src_path, install_path)
        elif args.command == "stop":
            print(f"Stopping app: {config_data['name']}-{version}")
            return handle_stop(install_path)
        elif args.command == "restart":
            print(f"Restarting app: {config_data['name']}-{version}")
            return handle_restart(src_path, install_path)
        else:
            raise ValueError('Invalid operation passed.')

    except Exception as ex:
        print(f'Error: {ex}')
        return 1

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