# This script is triggered by a Satbot, but can also be run in isolation:
# You'll need to run against the API's python3
# e.g. api/virtual_environments/venv/bin/python3 deploy.py
##
# You'll be prompted for the app and branch names.  There's also  the ability to
# deploy using manually-placed artifacts - see the instructions for 'branch' when you don't
# pass arguments in.
#
# It is expected that there is only one app of each type per environment.
#
# Environment settings are stored in .env in the same directory as this file.
# - copy the .env.example file

import os
import subprocess
import sys
from os.path import exists
import json
from pathlib import Path
from dotenv import dotenv_values
from datetime import datetime
from sqlalchemy import text
import time
sys.path.insert(0, str(Path(__file__).resolve().parent.parent.parent.parent.parent))
from app import app
from setup import DATABASE
from main.services.satbots.scripts.shared import run_command, output
from main.services.event_store import EventStore
from main.services.enums.user import EventCategory


def prepare_apps():
    return [app_name.lower() for app_name in DOTENV_VARS['APPS'].split(',')]

DOTENV_VARS = dotenv_values(os.path.dirname(__file__)+'/.env')
APPS = prepare_apps()
VARS = {}

os.system('clear')

def prepare_vars():
    for key, value in DOTENV_VARS.items():
        key = key.lower()
        for app_name in APPS:
            if key.startswith(app_name):
                if not VARS.get(app_name):
                    VARS[app_name.lower()] = {}
                VARS[app_name][key.replace(f"{app_name}_", '')] = value
            else:
                VARS[key] = value

def user_check():
    if subprocess.check_output('whoami').decode() != f"{VARS['user']}\n":
      raise ValueError(f"Please run this script as the user '{VARS['user']}'.")

def params():
    app_name = input(f"Which app do you want to deploy?  [{'|'.join(APPS)}]\n")
    if app_name not in APPS:
        raise ValueError("Invalid app name")

    app_vars = VARS[app_name]

    if exists(f"{app_vars['releases_path']}/manual_sat{app_name}_artifacts.zip") is True:
        output(satbot, f"{app_vars['releases_path']}/manual_sat{app_name}_artifacts.zip detected - will use this to deploy.\nDelete this file if you want to retreive a branch from GitLab.")
        branch = f"[manual_sat{app_name}_artifacts.zip]"
    else:
        output(satbot, f"NOTE: You can place a file containing artifacts named {app_vars['releases_path']}/manual_sat{app_name}_artifacts.zip to bypass retrieving a branch from GitLab")

        branch = input(f"Which branch should be deployed?  [default: {app_vars['default_branch']}]\n")
        branch = app_vars['default_branch'] if len(branch) == 0 else branch

    os.system('clear')
    return app_name, branch

def pre_deployment_checks(app_vars, branch):
    output(satbot, "================== DEPLOYMENT DETAILS ==================")
    for key, app_var in app_vars.items():
        output(satbot, f"{key.upper().rjust(15)}: {app_var}")
    output(satbot, f"{'SELECTED BRANCH'.ljust(15)}: {branch}")

def download_artifacts(app_vars, branch):
    output(satbot, "\n================== DOWNLOADING =========================")

    os.chdir(app_vars['releases_path'])

    timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
    extract_path = f"{app_vars['releases_path']}/{app_vars['name']}_{timestamp}"
    zip_file = f"{app_vars['name']}_artifacts_{timestamp}.zip"

    run_command(satbot, f"""
curl --location --output {zip_file} --header "PRIVATE-TOKEN: {VARS['gitlab_token']}" "https://gitlab.com/api/v4/projects/{app_vars['gitlab_id']}/jobs/artifacts/{branch}/download?job=create_artifacts"
    """)
    run_command(satbot, f"mkdir {extract_path}")
    run_command(satbot, f"unzip -q {zip_file} -d {extract_path}")
    run_command(satbot, f"rm {app_vars['releases_path']}/{zip_file}")

    os.chdir(extract_path)
    return extract_path

def uninstall_old_version(pip, name):
    run_command(satbot, f"{pip} uninstall satdom sat{name.replace('ext-', '')} {name.replace('ext-', '')} -y")

def install_new_version(pip, name, app_path, releases_path):
    if name == 'api':
        run_command(satbot, f"{pip} install -r {releases_path}/dist/api/requirements/production.txt")
        run_command(satbot, f"{pip} install {releases_path}/dist/api/api*.whl")

        for filename in ['app.py', 'config.py', 'config.yml', 'routes.py', 'setup.py', 'gunicorn_config.py']:
            run_command(satbot, f"cp {releases_path}/dist/api/{filename} {app_path}/app_home")
    else:
        run_command(satbot, f"{pip} install -r {releases_path}/dist/sat{name.replace('ext-', '')}/requirements/production.txt")
        run_command(satbot, f"{pip} install {releases_path}/dist/satdom/satdom-*.whl")
        run_command(satbot, f"{pip} install {releases_path}/dist/sat{name.replace('ext-', '')}/sat{name.replace('ext-', '')}-*.whl")

def copy_assets(app_path, releases_path):
    run_command(satbot, f"rm -rf {app_path}/public/static")
    run_command(satbot, f"mkdir -p {app_path}/public/static")
    run_command(satbot, f"cp -rv {releases_path}/dist/satportal/static/* {app_path}/public/static")

def restart_app_service(service):
    output(satbot, "================== RESTARTING =========================")
    run_command(satbot, f"sudo /usr/bin/systemctl restart {service}.service")

    time.sleep(10)

    if 'inactive' in subprocess.check_output(['systemctl', 'is-active', service]).decode():
        raise ValueError(f"{service} failed to restart - aborting so the remaining service stays active.  Manually start the {service} service, and check config for issues.")

def store_release_details(app_vars, branch):
    app_id = {
        'portal': 1,
        'admin': 2,
        'api': 3
    }

    data = {}
    if satbot:
        data['satbot_id'] = satbot.id

    with app.app_context():
        with DATABASE.engine.connect() as connection:
            connection.execute(
                text(
                    f"INSERT INTO releases(app, branch, data) VALUES ({app_id[app_vars['name']]}, '{branch}', '{json.dumps(data)}');"
                )
            )
            connection.commit()
            connection.close()
            

def migrate_databases(app_vars, release_path):
    output(satbot, "\n================== MIGRATING DATABASES =========================")

    if VARS['migrate_databases'] == 'true':
        run_command(satbot, f"cp -r dist/api/migrations {app_vars['app_paths']}/app_home")

        os.chdir(f"{app_vars['app_paths']}/app_home/migrations/users")
        run_command(satbot, f"{app_vars['pip_paths']}/alembic upgrade head")

        os.chdir(f"{app_vars['app_paths']}/app_home/migrations/insar")
        run_command(satbot, f"{app_vars['pip_paths']}/alembic upgrade head")

        os.chdir(f"{app_vars['app_paths']}/app_home/migrations/logs")
        run_command(satbot, f"{app_vars['pip_paths']}/alembic upgrade head")

        os.chdir(release_path)
    else:
        output(satbot, 'Database migrations disabled in .env')

def main(incoming_satbot=None, args=None):
    prepare_vars()

    global satbot
    satbot = incoming_satbot

    user_check()
    
    if satbot:
        args = {
            'app_name': satbot.decoded_parameters['app'],
            'branch': satbot.decoded_parameters['branch']
        }

    app_name = args['app_name'] if args and args.get('app_name') else None
    branch = args['branch'] if args and args.get('branch') else None

    if not app_name or not branch:
        app_name, branch = params()

    app_vars = VARS[app_name]

    app_paths = app_vars['app_paths'].split(',')
    pip_paths = app_vars['pip_paths'].split(',')
    services = app_vars['services'].split(',')

    if len(app_paths) == 1: # Admin, API
        pre_deployment_checks(app_vars, branch)
        os.chdir(app_paths[0])
        extract_path = download_artifacts(app_vars, branch)
        uninstall_old_version(f"{pip_paths[0]}/{app_vars['pip']}", app_vars['name'])
        install_new_version(f"{pip_paths[0]}/{app_vars['pip']}", app_vars['name'], app_paths[0], extract_path)

        if app_vars['name'] == 'api':
            run_command(satbot, f"cp -r dist/api/main {app_vars['app_paths']}/app_home")
            run_command(satbot, f"cp -r dist/api/examples {app_vars['app_paths']}/app_home")

            migrate_databases(app_vars, extract_path)

        for service in services:
            restart_app_service(service)

    else:   # Portal, External API
        pre_deployment_checks(app_vars, branch)
        extract_path = download_artifacts(app_vars, branch)

        for index, app_path in enumerate(app_paths):
            output(satbot, f"{index}: {app_path}")
            os.chdir(app_path)

            uninstall_old_version(f"{pip_paths[index]}/{app_vars['pip']}", app_vars['name'])
            install_new_version(f"{pip_paths[index]}/{app_vars['pip']}", app_vars['name'], app_path, extract_path)

            if app_vars['name'] == 'portal':
                copy_assets(app_path, extract_path)
        
            restart_app_service(services[index])

    output(satbot, "======================================================")

    try:
        store_release_details(app_vars, branch)
        output(satbot, "Deployment completed.")
    except:
        output(satbot, "Deployment completed, but failed to update status.")

    EventStore(
        user_agent='API',
        category=EventCategory.APP_DEPLOYED,
        public_data={
            "message": f"{app_vars['name'].replace('_', ' ').title().replace('Api', 'API')} Deployed [{branch}]"
        }
    )

if __name__ == "__main__":
    main()
