# -*- coding: utf-8 -*-

import os
import shutil
import subprocess

from . import tools
from .config import Config

VERSION = '0.0.1'

ISSUE_FILE = '/etc/issue'
NGINX_DIR = '/etc/nginx/conf.d'
ACME_CRON = '/etc/periodic/daily/acme-sh'
CERT_PUB_FILE = '/etc/ssl/services.pem'
CERT_KEY_FILE = '/etc/ssl/services.key'
CERT_SAN_FILE = '/etc/ssl/san.cnf'

NGINX_TEMPLATE = '''server {{
    listen [::]:{port} ssl http2;
    server_name {host}.{domain};

    access_log /var/log/nginx/{app}.access.log;
    error_log /var/log/nginx/{app}.error.log;

    location / {{
        proxy_pass http://{app}:8080;
    }}

    error_page 502 /502.html;
    location = /502.html {{
        root /usr/share/vmmgr/templates;
    }}

    location = /vm-ping {{
        add_header Content-Type text/plain;
        return 200 "vm-pong";
    }}
}}
'''

NGINX_DEFAULT_TEMPLATE = '''server {{
    listen [::]:80 default_server ipv6only=off;

    location / {{
        return 301 https://$host:{port}$request_uri;
    }}

    location /.well-known/acme-challenge/ {{
        root /etc/acme.sh.d;
    }}

    location = /vm-ping {{
        add_header Content-Type text/plain;
        return 200 "vm-pong";
    }}
}}

server {{
    listen [::]:{port} ssl http2 default_server ipv6only=off;

    location / {{
        proxy_pass http://127.0.0.1:8080;
    }}

    location /static {{
        root /usr/share/vmmgr;
    }}

    error_page 502 /502.html;
    location = /502.html {{
        root /usr/share/vmmgr/templates;
    }}

    location = /vm-ping {{
        add_header Content-Type text/plain;
        return 200 "vm-pong";
    }}
}}

server {{
    listen [::]:{port} ssl http2;
    server_name ~^(.*)\.{domain_esc}$;

    location / {{
        return 503;
    }}

    location /static {{
        root /usr/share/vmmgr;
    }}

    error_page 503 /503.html;
    location = /503.html {{
        root /usr/share/vmmgr/templates;
    }}

    location = /vm-ping {{
        add_header Content-Type text/plain;
        return 200 "vm-pong";
    }}
}}
'''

ISSUE_TEMPLATE = '''
\x1b[1;32m   _____             _   _         __      ____  __ 
  / ____|           | | | |        \\\\ \\\\    / /  \\\\/  |
 | (___  _ __   ___ | |_| |_ ___ _ _\\\\ \\\\  / /| \\\\  / |
  \\\\___ \\\\| '_ \\\\ / _ \\\\| __| __/ _ \\\\ '__\\\\ \\\\/ / | |\\\\/| |
  ____) | |_) | (_) | |_| ||  __/ |   \\\\  /  | |  | |
 |_____/| .__/ \\\\___/ \\\\__|\\\\__\\\\___|_|    \\\\/   |_|  |_|
        | |                                         
        |_|\x1b[0m

 \x1b[1;33mUPOZORNĚNÍ:\x1b[0m Neoprávněný přístup k tomuto zařízení je zakázán.
 Musíte mít výslovné oprávnění k přístupu nebo konfiguraci tohoto zařízení.
 Neoprávněné pokusy a kroky k přístupu nebo používání tohoto systému mohou mít
 za následek občanské nebo trestní sankce.

 \x1b[1;33mCAUTION:\x1b[0m Unauthozired access to this device is prohibited.
 You must have explicit, authorized permission to access or configure this
 device. Unauthorized attempts and actions to access or use this system may
 result in civil or criminal penalties.

 Pro přístup k aplikacím otevřete jednu z těcho URL v internetovém prohlížeči.
 Open one the following URLs in web browser to access the applications.

   - \x1b[1m{url}\x1b[0m
   - \x1b[1m{ip}\x1b[0m\x1b[?1c
'''

ACME_CRON_TEMPLATE = '''#!/bin/sh

[ -x /usr/bin/acme.sh ] && /usr/bin/acme.sh --cron >/dev/null
'''

CERT_SAN = '''[ req ]
distinguished_name = dn
x509_extensions = ext
[ dn ]
[ ext ]
subjectAltName=DNS:{domain},DNS:*.{domain}"
'''

class VMMgr:
    def __init__(self, conf):
        # Load JSON configuration
        self.conf = conf
        self.domain = conf['host']['domain']
        self.port = conf['host']['port']

    def register_app(self, app, login, password):
        # Write a file with credentials of a newly installed application which 
        # will be picked up by thread performing the installation after the install script finishes
        login = login if login else 'N/A'
        password = password if password else 'N/A'
        with open('/tmp/{}.credentials'.format(app), 'w') as f:
            f.write('{}\n{}'.format(login, password))

    def prepare_container(self):
        # Extract the variables from values given via lxc.hook.pre-start hook
        app = os.environ['LXC_NAME']
        # Remove ephemeral layer data
        tools.clean_ephemeral_layer(app)
        # Configure host and common params used in the app
        self.configure_app(app)

    def register_container(self):
        # Extract the variables from values given via lxc.hook.start-host hook
        app = os.environ['LXC_NAME']
        pid = os.environ['LXC_PID']
        # Lease the first unused IP to the container
        ip = tools.update_hosts_lease(app, True)
        tools.set_container_ip(pid, ip)

    def unregister_container(self):
        # Extract the variables from values given via lxc.hook.post-stop hook
        app = os.environ['LXC_NAME']
        # Release the container IP
        tools.update_hosts_lease(app, False)
        # Remove ephemeral layer data
        tools.clean_ephemeral_layer(app)

    def configure_app(self, app):
        script = os.path.join('/srv', app, 'update-conf.sh')
        if os.path.exists(script):
            setup_env = os.environ.copy()
            setup_env['DOMAIN'] = self.domain
            setup_env['PORT'] = self.port
            setup_env['EMAIL'] = self.conf['common']['email']
            setup_env['GMAPS_API_KEY'] = self.conf['common']['gmaps-api-key']
            subprocess.run([script], env=setup_env, check=True)

    def register_proxy(self, app):
        # Setup proxy configuration and reload nginx
        with open(os.path.join(NGINX_DIR, '{}.conf'.format(app)), 'w') as f:
            f.write(NGINX_TEMPLATE.format(app=app, host=self.conf['packages'][app]['host'], domain=self.domain, port=self.port))
        tools.reload_nginx()

    def unregister_proxy(self, app):
        # Remove proxy configuration and reload nginx
        os.unlink(os.path.join(NGINX_DIR, '{}.conf'.format(app)))
        tools.reload_nginx()

    def update_host(self, domain, port):
        # Update domain and port and rebuild all configuration. Web interface calls tools.restart_nginx() in WSGI close handler
        self.domain = self.conf['host']['domain'] = domain
        self.port = self.conf['host']['port'] = port
        self.conf.save()
        # Rebuild and restart nginx if it was requested. 
        self.rebuild_nginx()

    def rebuild_nginx(self):
        # Rebuild nginx config for the portal app. Web interface calls tools.restart_nginx() in WSGI close handler
        with open(os.path.join(NGINX_DIR, 'default.conf'), 'w') as f:
            f.write(NGINX_DEFAULT_TEMPLATE.format(port=self.port, domain_esc=self.domain.replace('.', '\.')))

    def rebuild_issue(self):
        # Compile the URLs displayed in terminal banner and rebuild the file
        with open(ISSUE_FILE, 'w') as f:
            f.write(ISSUE_TEMPLATE.format(url=tools.compile_url(self.domain, self.port), ip=tools.compile_url(tools.get_local_ip(), self.port)))

    def update_password(self, oldpassword, newpassword):
        # Update LUKS password and adminpwd for WSGI application
        input = '{}\n{}'.format(oldpassword, newpassword).encode()
        subprocess.run(['cryptsetup', 'luksChangeKey', '/dev/sda2'], input=input, check=True)
        # Update bcrypt-hashed password in config
        self.conf['host']['adminpwd'] = tools.adminpwd_hash(newpassword)
        # Save config to file
        self.conf.save()

    def create_selfsigned_cert(self):
        # Remove acme.sh cronjob
        if os.path.exists(ACME_CRON):
            os.unlink(ACME_CRON)
        # Create selfsigned certificate with wildcard alternative subject name
        with open(os.path.join(CERT_SAN_FILE), 'w') as f:
            f.write(CERT_SAN.format(domain=self.domain))
        subprocess.run(['openssl', 'req', '-config', CERT_SAN_FILE, '-x509', '-new', '-out', CERT_PUB_FILE, '-keyout', CERT_KEY_FILE, '-nodes', '-days', '7305', '-subj', '/CN={}'.format(self.domain)], check=True)
        os.chmod(CERT_KEY_FILE, 0o640)

    def request_acme_cert(self):
        # Remove all possible conflicting certificates requested in the past
        certs = [i for i in os.listdir('/etc/acme.sh.d') if i not in ('account.conf', 'ca', 'http.header')]
        for cert in certs:
            if cert != self.domain:
                subprocess.run(['/usr/bin/acme.sh', '--remove', '-d', cert])
        # Compile an acme.sh command for certificate requisition only if the certificate hasn't been requested before
        if not os.path.exists(os.path.join('/etc/acme.sh.d', self.domain)):
            cmd = ['/usr/bin/acme.sh', '--issue', '-d', self.domain]
            for app in self.conf['apps'].copy():
                cmd += ['-d', '{}.{}'.format(self.conf['packages'][app]['host'], self.domain)]
            cmd += ['-w', '/etc/acme.sh.d']
            # Request the certificate
            subprocess.run(cmd, check=True)
        # Otherwise just try to renew
        else:
            # Acme.sh returns code 2 on skipped renew
            try: 
                subprocess.run(['/usr/bin/acme.sh', '--renew', '-d', self.domain], check=True)
            except subprocess.CalledProcessError as e:
                if e.returncode != 2:
                    raise
        # Install the issued certificate
        subprocess.run(['/usr/bin/acme.sh', '--install-cert', '-d', self.domain, '--key-file', CERT_KEY_FILE, '--fullchain-file', CERT_PUB_FILE, '--reloadcmd', '/sbin/service nginx reload'], check=True)
        # Install acme.sh cronjob
        with open(ACME_CRON, 'w') as f:
            f.write(ACME_CRON_TEMPLATE)

    def install_manual_cert(self, public_file, private_file):
        # Remove acme.sh cronjob
        if os.path.exists(ACME_CRON):
            os.unlink(ACME_CRON)
        # Copy certificate files
        shutil.copyfile(public_file, CERT_PUB_FILE)
        shutil.copyfile(private_file, CERT_KEY_FILE)
        os.chmod(CERT_KEY_FILE, 0o640)
        # Reload nginx
        tools.reload_nginx()
