#!/usr/bin/env python
import os,socket, sys
import json

from fabric.state import _AttributeDict, env
from fabric.operations import run, sudo
from fabric.context_managers import cd, settings
from fabric.contrib.files import append, contains, exists
from fabric.decorators import runs_once

from woven.decorators import run_once_per_version
from woven.deployment import deploy_files, mkdirs, upload_template
from woven.environment import deployment_root, version_state, _root_domain, get_packages
from woven.linux import add_user

def _activate_sites(path, filenames):
    enabled_sites = _ls_sites(path)            
    for site in enabled_sites:
        if env.verbosity:
            print env.host,'Disabling', site
        if site not in filenames:
            sudo("rm %s/%s"% (path,site))
        
        sudo("chmod 644 %s" % site)
        if not exists('/etc/apache2/sites-enabled'+ filename):
            sudo("ln -s %s%s %s%s"% (self.deploy_root,filename,self.enabled_path,filename))

def _deploy_webconf(remote_dir,template):
    
    if not 'http:' in env.MEDIA_URL: media_url = env.MEDIA_URL
    else: media_url = ''
    if not 'http:' in env.STATIC_URL: static_url = env.STATIC_URL
    else: static_url = ''
    if not static_url: static_url = env.ADMIN_MEDIA_PREFIX
    log_dir = '/'.join([deployment_root(),'log'])
    deployed = []
    users_added = []
    
    domains = domain_sites()
    for d in domains:
        u_domain = d.name.replace('.','_')
        wsgi_filename = d.settings.replace('.py','.wsgi')
        site_user = ''.join(['site_',str(d.site_id)])
        filename = ''.join([remote_dir,'/',u_domain,'-',env.project_version,'.conf'])
        context = {"project_name": env.project_name,
                   "deployment_root":deployment_root(),
                    "u_domain":u_domain,
                    "domain":d.name,
                    "root_domain":env.root_domain,
                    "user":env.user,
                    "site_user":site_user,
                    "SITE_ID":d.site_id,
                    "host_ip":socket.gethostbyname(env.host),
                    "wsgi_filename":wsgi_filename,
                    "MEDIA_URL":media_url,
                    "STATIC_URL":static_url,
                    }

        upload_template('/'.join(['woven',template]),
                        filename,
                        context,
                        use_sudo=True)
        if env.verbosity:
            print " * uploaded", filename
            
        #add site users if necessary
        site_users = _site_users()
        if site_user not in users_added and site_user not in site_users:
            add_user(username=site_user,group='www-data',site_user=True)
            users_added.append(site_user)
            if env.verbosity:
                print " * useradded",site_user

    return deployed

def _site_users():
    """
    Get a list of site_n users
    """
    userlist = sudo("cat /etc/passwd | awk '/site/'").split('\n')
    siteuserlist = [user.split(':')[0] for user in userlist if 'site_' in user]
    return siteuserlist

def _ls_sites(path):
    """
    List only sites in the domain_sites() to ensure we co-exist with other projects
    """
    with cd(path):
        sites = run('ls').split('\n')
        doms =  [d.name for d in domain_sites()]
        dom_sites = []
        for s in sites:
            ds = s.split('-')[0]
            ds = ds.replace('_','.')
            if ds in doms and s not in dom_sites:
                dom_sites.append(s)
    return dom_sites



def _sitesettings_files():
    """
    Get a list of sitesettings files
    
    settings.py can be prefixed with a subdomain and underscore so with example.com site:
    sitesettings/settings.py would be the example.com settings file and
    sitesettings/admin_settings.py would be the admin.example.com settings file
    """
    settings_files = []
    sitesettings_path = os.path.join(env.project_package_name,'sitesettings')
    if os.path.exists(sitesettings_path):
        sitesettings = os.listdir(sitesettings_path)
        for file in sitesettings:
            if file == 'settings.py':
                settings_files.append(file)
            elif len(file)>12 and file[-12:]=='_settings.py': #prefixed settings
                settings_files.append(file)
    return settings_files

def _get_django_sites():
    """
    Get a list of sites as dictionaries {site_id:'domain.name'}

    """
    deployed = version_state('deploy_project')
    if not env.sites and 'django.contrib.sites' in env.INSTALLED_APPS and deployed:
        with cd('/'.join([deployment_root(),'env',env.project_fullname,'project',env.project_package_name,'sitesettings'])):
            venv = '/'.join([deployment_root(),'env',env.project_fullname,'bin','activate'])
            #since this is the first time we run ./manage.py on the server it can be
            #a point of failure for installations
            with settings(warn_only=True):
                output = run(' '.join(['source',venv,'&&',"django-admin.py dumpdata sites --settings=%s.sitesettings.settings"% env.project_package_name]))

                if output.failed:
                    print "ERROR: There was an error running ./manage.py on the node"
                    print "See the troubleshooting docs for hints on how to diagnose deployment issues"
                    if hasattr(output, 'stderr'):
                        print output.stderr
                    sys.exit(1)
            output = output.split('\n')[-1] #ignore any lines prior to the data being dumped
            sites = json.loads(output)
            env.sites = {}
            for s in sites:
                env.sites[s['pk']] = s['fields']['domain']
    return env.sites

def domain_sites():
    """
    Get a list of domains
    
    Each domain is an attribute dict with name, site_id and settings
    """

    if not hasattr(env,'domains'):
        sites = _get_django_sites()
        site_ids = sites.keys()
        site_ids.sort()
        domains = []
        
        for id in site_ids:

            for file in _sitesettings_files():
                domain = _AttributeDict({})

                if file == 'settings.py':
                    domain.name = sites[id]
                else: #prefix indicates subdomain
                    subdomain = file[:-12].replace('_','.')
                    domain.name = ''.join([subdomain,sites[id]])

                domain.settings = file
                domain.site_id = id
                domains.append(domain)
                
        env.domains = domains
        if env.domains: env.root_domain = env.domains[0].name
        else:
            domain.name = _root_domain(); domain.site_id = 1; domain.settings='settings.py'
            env.domains = [domain]
            
    return env.domains

@run_once_per_version
def deploy_webconf():
    """ Deploy nginx and other wsgi server site configurations to the host """
    deployed = []
    log_dir = '/'.join([deployment_root(),'log'])
    #TODO - incorrect - check for actual package to confirm installation
    if webserver_list():
        if env.verbosity:
            print env.host,"DEPLOYING webconf:"
        if not exists(log_dir):
            run('ln -s /var/log log')
        #deploys confs for each domain based on sites app
        if 'apache2' in get_packages():
            deployed += _deploy_webconf('/etc/apache2/sites-available','django-apache-template.txt')
            deployed += _deploy_webconf('/etc/nginx/sites-available','nginx-template.txt')
        elif 'gunicorn' in get_packages():
            deployed += _deploy_webconf('/etc/nginx/sites-available','nginx-gunicorn-template.txt')
            
        upload_template('woven/maintenance.html','/var/www/nginx-default/maintenance.html',use_sudo=True)
        sudo('chmod ugo+r /var/www/nginx-default/maintenance.html')
    else:
        print env.host,"""WARNING: Apache or Nginx not installed"""
        
    return deployed

@run_once_per_version
def deploy_wsgi():
    """
    deploy python wsgi file(s)
    """ 
    if 'libapache2-mod-wsgi' in get_packages():
        remote_dir = '/'.join([deployment_root(),'env',env.project_fullname,'wsgi'])
        wsgi = 'apache2'
    elif 'gunicorn' in get_packages():
        remote_dir = '/etc/init'
        wsgi = 'gunicorn'
    deployed = []
    
    #ensure project apps path is also added to environment variables as well as wsgi
    if env.PROJECT_APPS_PATH:
        pap = '/'.join([deployment_root(),'env',
                        env.project_name,'project',env.project_package_name,env.PROJECT_APPS_PATH])
        pap = ''.join(['export PYTHONPATH=$PYTHONPATH:',pap])
        postactivate = '/'.join([deployment_root(),'env','postactivate'])
        if not exists(postactivate):
            append('#!/bin/bash', postactivate)
            run('chmod +x %s'% postactivate)
        if not contains('PYTHONPATH',postactivate):
            append(pap,postactivate)
        
    if env.verbosity:
        print env.host,"DEPLOYING wsgi", wsgi, remote_dir

    for file in _sitesettings_files(): 
        deployed += mkdirs(remote_dir)
        with cd(remote_dir):
            settings_module = file.replace('.py','')
            context = {"deployment_root":deployment_root(),
                       "user": env.user,
                       "project_name": env.project_name,
                       "project_package_name": env.project_package_name,
                       "project_apps_path":env.PROJECT_APPS_PATH,
                       "settings": settings_module,
                       }
            if wsgi == 'apache2':
                filename = file.replace('.py','.wsgi')
                upload_template('/'.join(['woven','django-wsgi-template.txt']),
                                filename,
                                context,
                            )
            elif wsgi == 'gunicorn':
                filename = 'gunicorn-%s.conf'% env.project_name
                upload_template('/'.join(['woven','gunicorn.conf']),
                                filename,
                                context,
                                backup=False,
                                use_sudo=True
                            )                
                
            if env.verbosity:
                print " * uploaded", filename
            #finally set the ownership/permissions
            #We'll use the group to allow www-data execute
            if wsgi == 'apache2':
                sudo("chown %s:www-data %s"% (env.user,filename))
                run("chmod ug+xr %s"% filename)
            elif wsgi == 'gunicorn':
                sudo("chown root:root %s"% filename)
                sudo("chmod go+r %s"% filename)
                
    return deployed

def webserver_list():
    """
    list of webserver packages
    """
    p = set(get_packages())
    w = set(['apache2','gunicorn','uwsgi','nginx'])
    installed = p & w
    return list(installed)
    
def reload_webservers():
    """
    Reload apache2 and nginx
    """
    if env.verbosity:
        print env.host, "RELOADING apache2"
    with settings(warn_only=True):
        a = sudo("/etc/init.d/apache2 reload")
        if env.verbosity:
            print '',a        
    if env.verbosity:

        #Reload used to fail on Ubuntu but at least in 10.04 it works
        print env.host,"RELOADING nginx"
    with settings(warn_only=True):
        s = run("/etc/init.d/nginx status")
        if 'running' in s:
            n = sudo("/etc/init.d/nginx reload")
        else:
            n = sudo("/etc/init.d/nginx start")
    if env.verbosity:
        print ' *',n
    return True    

def stop_webserver(server):
    """
    Stop server
    """
    #TODO - distinguish between a warning and a error on apache
    if server == 'apache2':
        with settings(warn_only=True):
            if env.verbosity:
                print env.host,"STOPPING apache2"
            a = sudo("/etc/init.d/apache2 stop")
            if env.verbosity:
                print '',a
    elif server == 'gunicorn':
        with settings(warn_only=True):
            if env.verbosity:
                print env.host,"STOPPING","%s-%s"% (server,env.project_name)
            a = sudo("stop %s-%s"% (server,env.project_name))
            if env.verbosity and a.strip():
                print '',a
    return True

def start_webserver(server):
    """
    Start server
    """
    if server == 'apache2':
        with settings(warn_only=True):
            if env.verbosity:
                print env.host,"STARTING apache2"
            a = sudo("/etc/init.d/apache2 start")
            if env.verbosity:
                print '',a
            
        if a.failed:
            print "ERROR: /etc/init.d/apache2 start failed"
            print env.host, a
            sys.exit(1)
    elif server == 'nginx':
        if env.verbosity:
            #Reload used to fail on Ubuntu but at least in 10.04 it works
            print env.host,"RELOADING nginx"
        with settings(warn_only=True):
            s = run("/etc/init.d/nginx status")
            if 'running' in s:
                n = sudo("/etc/init.d/nginx reload")
            else:
                n = sudo("/etc/init.d/nginx start")
        if env.verbosity:
            print ' *',n
    else:
        if env.verbosity:
            print env.host, "STARTING","%s-%s"% (server,env.project_name)
        with settings(warn_only=True):
            n = sudo('start %s-%s'% (server,env.project_name))
            if env.verbosity and n.strip():
                print ' *', n
            
    return True

    