import os
import textwrap
from os.path import expanduser

from django.conf import settings
from django.core.management.base import BaseCommand, CommandError

from orchestra.utils import paths
from orchestra.utils.sys import run, check_root, confirm


class Command(BaseCommand):
    help = 'Configures nginx + uwsgi to run with your Orchestra instance.'

    def __init__(self, *args, **kwargs):
        super(Command, self).__init__(*args, **kwargs)

    def add_arguments(self, parser):
        parser.add_argument(
            '--cert', 
            dest='cert',
            default='',
            help='Nginx SSL certificate, one will be created by default.',
        )
        parser.add_argument(
            '--cert-key', 
            dest='cert_key', 
            default='',
            help='Nginx SSL certificate key.'
        )
        parser.add_argument(
            '--cert-path', 
            dest='cert_path',
            default=os.path.join(paths.get_site_dir(), 'ssl', 'orchestra.crt'),
            help='Nginx SSL certificate, one will be created by default.'
        )
        parser.add_argument(
            '--cert-key-path', 
            dest='cert_key_path',
            default=os.path.join(paths.get_site_dir(), 'ssl', 'orchestra.key'),
            help='Nginx SSL certificate key.'
        )
        parser.add_argument(
            '--cert-override', 
            dest='cert_override',
            action='store_true',
            default=False, help='Force override cert and keys if exists.'
        )
        parser.add_argument(
            '--cert-country', 
            dest='cert_country',
            default='ES',
            help='Certificate Distinguished Name Country.'
        )
        parser.add_argument(
            '--cert-state', 
            dest='cert_state', 
            default='Spain',
            help='Certificate Distinguished Name STATE.'
        )
        parser.add_argument(
            '--cert-locality', 
            dest='cert_locality', 
            default='Barcelona',
            help='Certificate Distinguished Name Country.'
        )
        parser.add_argument(
            '--cert-org_name', 
            dest='cert_org_name', 
            default='Orchestra',
            help='Certificate Distinguished Name Organization Name.'
        )
        parser.add_argument(
            '--cert-org_unit', 
            dest='cert_org_unit', 
            default='DevOps',
            help='Certificate Distinguished Name Organization Unity.'
        )
        parser.add_argument(
            '--cert-email', 
            dest='cert_email', 
            default='orchestra@orchestra.lan',
            help='Certificate Distinguished Name Email Address.'
        )
        parser.add_argument(
            '--cert-common_name', 
            dest='cert_common_name', 
            default=None,
            help='Certificate Distinguished Name Common Name.'
        )
        parser.add_argument(
            '--server-name', 
            dest='server_name', 
            default='',
            help='Nginx SSL certificate key.'
        )
        parser.add_argument(
            '--user', 
            dest='user', 
            default='',
            help='uWSGI daemon user.'
        )
        parser.add_argument(
            '--group', 
            dest='group', 
            default='',
            help='uWSGI daemon group.'
        )
        parser.add_argument(
            '--processes', 
            dest='processes', 
            default=4,
            help='uWSGI number of processes.'
        )
        parser.add_argument(
            '--noinput', 
            action='store_false', 
            dest='interactive', 
            default=True,
            help='''Tells Django to NOT prompt the user for input of any kind. 
                    You must use --username with --noinput, and must contain the 
                    cleeryd process owner, which is the user how will perform tincd updates'''
        )
    
    def generate_certificate(self, **options):
        override = options.get('cert_override')
        interactive = options.get('interactive')
        
        cert = options.get('cert')
        key = options.get('cert_key')
        if bool(cert) != bool(key):
            raise CommandError("--cert and --cert-key go in tandem")
        
        cert_path = options.get('cert_path')
        key_path = options.get('cert_key_path')
        
        run('mkdir -p %s' % os.path.dirname(cert_path))
        exists = os.path.isfile(cert_path)
        
        if not override and exists:
            self.stdout.write('Your cert and keys are already in place.')
            self.stdout.write('Use --override in order to regenerate them.')
            return cert_path, key_path
        
        common_name = options.get('cert_common_name') or options.get('server_name') or 'orchestra.lan'
        country = options.get('cert_country')
        state = options.get('cert_state')
        locality = options.get('cert_locality')
        org_name = options.get('cert_org_name')
        org_unit = options.get('cert_org_unit')
        email = options.get('cert_email')
        if interactive:
            msg = ('-----\n'
                'You are about to be asked to enter information that\n'
                'will be incorporated\n'
                'into your certificate request.\n'
                'What you are about to enter is what is called a\n'
                'Distinguished Name or a DN.\n'
                'There are quite a few fields but you can leave some blank\n'
                '-----\n')
            self.stdout.write(msg)
            
            msg = 'Country Name (2 letter code) [%s]: ' % country
            country = input(msg) or country
            
            msg = 'State or Province Name (full name) [%s]: ' % state
            state = input(msg) or state
            
            msg = 'Locality Name (eg, city) [%s]: ' % locality
            locality = input(msg) or locality
            
            msg = 'Organization Name (eg, company) [%s]: ' % org_name
            org_name = input(msg) or org_name
            
            msg = 'Organizational Unit Name (eg, section) [%s]: ' % org_unit
            org_unit = input(msg) or org_unit
            
            msg = 'Email Address [%s]: ' % email
            email = input(msg) or email
            
        self.stdout.write('Common Name: %s' % common_name)
        subject = {
            'C': country,
            'S': state,
            'L': locality,
            'O': org_name,
            'OU': org_unit,
            'Email': email,
            'CN': common_name,
        }
        context = {
            'subject': ''.join(('/%s=%s' % (k,v) for k,v in subject.items())),
            'key_path': key_path,
            'cert_path': cert_path,
            'cert_root': os.path.dirname(cert_path),
        }
        self.stdout.write('writing new cert to \'%s\'' % cert_path)
        self.stdout.write('writing new cert key to \'%s\'' % key_path)
        run(textwrap.dedent("""\
            openssl req -x509 -nodes -days 365 -newkey rsa:4096 -keyout %(key_path)s -out %(cert_path)s -subj "%(subject)s"
            chown --reference=%(cert_root)s %(cert_path)s %(key_path)s\
            """) % context, display=True
        )
        return cert_path, key_path
    
    @check_root
    def handle(self, *args, **options):
        user = options.get('user')
        if not user:
            raise CommandError("System user for running uwsgi must be provided.")
        
        cert_path, key_path = self.generate_certificate(**options)
        server_name = options.get('server_name')
        
        context = {
            'cert_path': cert_path,
            'key_path': key_path,
            'project_name': paths.get_project_name(),
            'project_dir': paths.get_project_dir(),
            'site_dir': paths.get_site_dir(),
            'static_root': settings.STATIC_ROOT,
            'static_url': (settings.STATIC_URL or '/static').rstrip('/'),
            'user': user,
            'group': options.get('group') or user,
            'home': expanduser("~%s" % options.get('user')),
            'processes': int(options.get('processes')),
            'server_name': 'server_name %s' % server_name if server_name else ''
        }
        
        nginx_conf = textwrap.dedent("""\
            server {
                listen 80;
                listen [::]:80 ipv6only=on;
                return 301 https://$host$request_uri;
            }
            
            server {
                listen 443 ssl;
                # listen [::]:443 ssl; # add SSL support to IPv6 address
                %(server_name)s
                ssl_certificate %(cert_path)s;
                ssl_certificate_key %(key_path)s;
                rewrite ^/$ /admin/;
                client_max_body_size 16m;
                location / {
                    uwsgi_pass unix:///var/run/uwsgi/app/%(project_name)s/socket;
                    include uwsgi_params;
                }
                location %(static_url)s {
                    alias %(static_root)s;
                    expires 30d;
                }
            }
            """
        ) % context
        
        uwsgi_conf = textwrap.dedent("""\
            [uwsgi]
            plugins        = python3
            chdir          = %(site_dir)s
            module         = %(project_name)s.wsgi
            master         = true
            workers        = %(processes)d
            chmod-socket   = 664
            stats          = /run/uwsgi/%%(deb-confnamespace)/%%(deb-confname)/statsocket
            uid            = %(user)s
            gid            = %(group)s
            env            = HOME=%(home)s
            touch-reload   = %(project_dir)s/wsgi.py
            vacuum         = true    # Remove socket stop
            enable-threads = true    # Initializes the GIL
            max-requests   = 500     # Mitigates memory leaks
            lazy-apps      = true    # Don't share database connections
            """
        ) % context
        
        nginx_file = '/etc/nginx/sites-available/%(project_name)s.conf' % context
        if server_name:
            context['server_name'] = server_name
            nginx_file = '/etc/nginx/sites-available/%(server_name)s.conf' % context
        nginx = {
            'file': nginx_file,
            'conf': nginx_conf
        }
        uwsgi = {
            'file': '/etc/uwsgi/apps-available/%(project_name)s.ini' % context,
            'conf': uwsgi_conf
        }
        
        interactive = options.get('interactive')
        for extra_context in (nginx, uwsgi):
            context.update(extra_context)
            diff = run("cat << 'EOF' | diff - %(file)s\n%(conf)s\nEOF" % context, valid_codes=(0,1,2))
            if diff.exit_code == 2:
                # File does not exist
                run("cat << 'EOF' > %(file)s\n%(conf)s\nEOF" % context, display=True)
            elif diff.exit_code == 1:
                # File is different, save the old one
                if interactive:
                    if not confirm("\n\nFile %(file)s be updated, do you like to overide "
                                   "it? (yes/no): " % context):
                        return
                run(textwrap.dedent("""\
                    cp %(file)s %(file)s.save
                    cat << 'EOF' > %(file)s
                    %(conf)s
                    EOF""") % context, display=True
                )
                self.stdout.write("\033[1;31mA new version of %(file)s has been installed.\n "
                    "The old version has been placed at %(file)s.save\033[m" % context)
        
        if server_name:
            run('ln -s /etc/nginx/sites-available/%(server_name)s.conf /etc/nginx/sites-enabled/' % context,
                valid_codes=[0,1], display=True)
        else:
            run('rm -f /etc/nginx/sites-enabled/default')
            run('ln -s /etc/nginx/sites-available/%(project_name)s.conf /etc/nginx/sites-enabled/' % context,
                valid_codes=[0,1], display=True)
        run('ln -s /etc/uwsgi/apps-available/%(project_name)s.ini /etc/uwsgi/apps-enabled/' % context,
            valid_codes=[0,1], display=True)
        
        rotate = textwrap.dedent("""\
            /var/log/nginx/*.log {
                daily
                missingok
                rotate 30
                compress
                delaycompress
                notifempty
                create 640 root adm
                sharedscripts
                postrotate
                    [ ! -f /var/run/nginx.pid ] || kill -USR1 `cat /var/run/nginx.pid`
                endscript
            }"""
        )
        run("echo '%s' > /etc/logrotate.d/nginx" % rotate, display=True)
        
        # Allow nginx to write to uwsgi socket
        run('adduser www-data %(group)s' % context, display=True)