# -*- coding: utf-8 -*-

__author__ = 'heddevanderheide'

import datetime
import os, sys

from fabric.api import *
from fabric.context_managers import lcd

# todo's:
#
# - implement pip wheel (no need for pypi connections)
# - log level configuration (pip --quiet etc)
# - rolling back (reverse symlinks, remove release dir)
# - local build dir is useless, should all move to /tmp


# Main Configuration
env.hosts = ['www.example.com']
env.user = 'johndoe'
env.key_filename = '/Users/johndoe/.ssh/id_rsa.pub'


# Stage Configuration
class Test(object):
    """
    [items]
        mappings of django settings that should be parsed
        into the local_settings.py (or params.py), the keys
        should be prefixed 'settings_'
        
    [stage_root]
        the absolute path to your remote stage root
        
    [release_version]
        the branch or tag you wish to deploy
        
    [timestamp_format]
        the way release directories should look
        
    """
    items = dict(settings_DEBUG=True, settings_ENV='test').items()

    stage_root = '/home/johndoe/example.com/test.example.com'
    project_name = 'example'

    release_version = 'develop'
    timestamp_format = '%Y%m%d_%H%M%S'

    hosts = env.hosts
    user = env.user
    password = env.password


class Production(Test):
    items = dict(settings_DEBUG=False, settings_ENV='production').items()

    stage_root = '/home/johndoe/example.com/example.com'
    release_version = 'develop'


# Setup the Configuration Dict
ENV = {
    'test': Test(),
    'production': Production()
}


# # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# Tasks                                                 #
# # # # # # # # # # # # # # # # # # # # # # # # # # # # #

@task
def deploy(env='test'):
    try:
        env = ENV.get(env)

        remote_uname()

        release = get_timestamp(env)

        create_build_directory(env, release)
        create_deploy_directory(env)

        create_timestamp_directory(env, release)

        checkout_version(env, release)

        pack_code(env, release)

        upload_code(env, release)

        unpack_tarball(env, release)

        create_virtualenv(env, release)
        pip_install_requirements(env, release)

        django_set_envs_for_release(env, release)

        with cd('{stage_root}/RELEASES/{release}/src'.format(stage_root=env.stage_root, release=release)):
            django_sync_migrate(env, release)
            django_compilemessages(env, release)
            django_collectstatic(env, release)

        switch_symlinks(env, release)

    except Exception, e:
        print 'ERROR:', e
        print 'INFO: rolling back...'

    finally:
        clean(env)


# # # # # # # # # # # # # # # # # # # # # # # # # # # # #
# Functions                                             #
# # # # # # # # # # # # # # # # # # # # # # # # # # # # #

def remote_uname():
    # remote connection indicator
    run('uname -a')


def create_build_directory(env, release):
    # build dir

    local(
        'mkdir -p build/{project_name}/{release}'.format(
            project_name=env.project_name,
            release=release
        )
    )


def get_timestamp(env):
    # returns release timestamp
    
    return datetime.datetime.now().strftime(
        '{timestamp_format}'.format(timestamp_format=env.timestamp_format)
    )


def create_deploy_directory(env):
    # creates the remote release dir if it
    # doesn't already exist

    run(
        'mkdir -p {stage_root}/RELEASES'.format(
            stage_root=env.stage_root
        )
    )


def checkout_version(env, release):
    # checkout the version we want to deploy
    
    local(
        'git clone . /tmp/{release}'.format(
            release=release
        )
    )
    
    with lcd('/tmp/{release}'.format(release=release)):
        local(
            'git checkout {version}'.format(
                version=env.release_version
            )
        )


def pack_code(env, release):
    # tarball the version designated for deploy
    
    local('rm -rf /tmp/{release}/.git'.format(release=release))
    local(
        'tar czf build/{project_name}/{version}.tar.gz /tmp/{release}'.format(
            project_name=env.project_name,
            version=env.release_version,
            release=release
        )
    )


def upload_code(env, release):
    # upload tarball to the remote
    
    local('scp build/{project_name}/{release}.tar.gz {user}@{host}:{remote_path}'.format(
        project_name=env.project_name,
        host=env.hosts[0],
        user=env.user,
        release=env.release_version,
        remote_path='{stage_root}'.format(
            stage_root=env.stage_root,
            release=env.release_version
        )
    ))


def create_timestamp_directory(env, timestamp):
    # create remote timestamp directory for deploy

    run(
        'mkdir -p {stage_root}/RELEASES/{timestamp}'.format(
            stage_root=env.stage_root,
            timestamp=timestamp
        )
    )


def unpack_tarball(env, release):
    # unpacks tar release into remote release dir

    run(
        'tar xvf {stage_root}/{release_version}.tar.gz -C {stage_root}/RELEASES/{release} --strip-components=2'.format(
            stage_root=env.stage_root,
            release_version=env.release_version,
            release=release,
            project_name=env.project_name
        )
    )


def create_virtualenv(env, release):
    # creates a virtualenv inside the release dir

    run(
        'virtualenv {stage_root}/RELEASES/{release}/venv'.format(
            stage_root=env.stage_root,
            release=release
        )
    )


def pip_install_requirements(env, release):
    # install the requirements.txt using the venv
    
    run(
        '{stage_root}/RELEASES/{release}/venv/bin/pip install -r {stage_root}/RELEASES/{release}/requirements.txt --quiet'.format(
            stage_root=env.stage_root,
            release=release
        )
    )


def django_set_envs_for_release(env, release):
    # setup configured environment variables 
    # local_settings.py / params.py
    
    path = 'build/{project_name}/{release}'.format(
        project_name=env.project_name,
        release=release
    )

    local('touch {path}/local_settings.py'.format(path=path))

    f = open('{path}/local_settings.py'.format(path=path), 'w+')
    string = "\n".join(
        [
            "{key} = {value}".format(key=k[9:], value=False if v == '' else repr(v))
            for k, v in env.items if k.startswith('settings_')
        ]
    )
    f.write(string)
    f.close()

    local('scp {path}/local_settings.py {user}@{host}:{remote_path}'.format(
        path=path,
        project_name=env.project_name,
        host=env.hosts[0],
        user=env.user,
        release=env.release_version,
        remote_path='{stage_root}/RELEASES/{release}/src/main/'.format(
            stage_root=env.stage_root,
            release=release
        )
    ))


def django_collectstatic(env, release):
    run(
        '{stage_root}/RELEASES/{release}/venv/bin/python {stage_root}/RELEASES/{release}/src/manage.py collectstatic --noinput'.format(
            stage_root=env.stage_root,
            release=release
        )
    )


def django_compilemessages(env, release):
    run(
        '{stage_root}/RELEASES/{release}/venv/bin/python {stage_root}/RELEASES/{release}/src/manage.py compilemessages'.format(
            stage_root=env.stage_root,
            release=release
        )
    )


def django_sync_migrate(env, release):
    run(
        '{stage_root}/RELEASES/{release}/venv/bin/python {stage_root}/RELEASES/{release}/src/manage.py syncdb --migrate'.format(
            stage_root=env.stage_root,
            release=release
        )
    )


def switch_symlinks(env, release):
    symlink_wsgi = '{stage_root}/RELEASES/{release}/src/wsgi.py'.format(
        stage_root=env.stage_root,
        release=release
    )
    symlink_current = '{stage_root}/RELEASES/{release}'.format(
        stage_root=env.stage_root,
        release=release
    )

    run('touch {stage_root}/RELEASES/{release}/src/VERSION'.format(stage_root=env.stage_root, release=release))
    run(
        'echo "{release_version}" > {stage_root}/RELEASES/{release}/src/VERSION'.format(
            release_version=env.release_version,
            stage_root=env.stage_root,
            release=release,
            project_name=env.project_name
        )
    )
    run(
        'ln -fs {symlink} {stage_root}/wsgi.py'.format(
            stage_root=env.stage_root,
            release=release,
            symlink=symlink_wsgi
        )
    )
    run(
        'ln -snf {symlink} {stage_root}/current'.format(
            stage_root=env.stage_root,
            release=release,
            symlink=symlink_current
        )
    )


def clean(env):
    run(
        'ls -1dt {stage_root}/RELEASES/* | tail -n +6 | xargs rm -rf'.format(
            stage_root=env.stage_root
        )
    )