import sys
import commands
import subprocess
import socket
import os

from os import path

from cpbox.tool import utils
from cpbox.tool import datatypes
from cpbox.tool import file
from cpbox.tool import logger
from cpbox.tool import template
from cpbox.tool import array

from cpbox.app.appconfig import appconfig
from signal import signal, SIGPIPE, SIG_DFL

class DevOpsApp(object):

    def want_logger(self, app_name=None):
        syslog_ng_server = os.environ['PUPPY_SYSLOG_NG_SERVER'] if 'PUPPY_SYSLOG_NG_SERVER' in os.environ else None
        logger.make_logger(app_name, self.log_level, syslog_ng_server, True)
        return logger.getLogger(app_name)

    def __init__(self, app_name, log_level='info', exit_on_error=True):
        self.app_name = app_name
        appconfig.init(app_name)
        self.log_level = log_level
        self.exit_on_error = exit_on_error
        self.env = self.get_env()
        self.logger = self.want_logger(self.app_name)

        hostname = socket.gethostname()
        self.hostname_fqdn = hostname
        self.hostname_short = hostname.split('.', 1)[0]

        self.root_dir = path.dirname(path.realpath(sys.argv[0]))
        self.roles_dir = self.root_dir + '/roles'

        self.prepare_app_dirs()

        self.app_storage_dir = '/opt/data/' + self.app_name
        self.app_persitent_storage_dir = self.app_storage_dir + '/persistent'
        self.app_runtime_storage_dir = self.app_storage_dir + '/runtime'
        self.app_logs_dir = self.app_runtime_storage_dir + '/logs'

        self._ensure_dir_and_write_permision()

        self.file_lock = None
        self._hostvar = None

    def get_env(self):
        env = os.environ['PUPPY_ENV'] if 'PUPPY_ENV' in os.environ else 'dev'
        return env

    def prepare_app_dirs(self):
        app_root_dir = self.roles_dir + '/' + self.app_name

        self.app_config_dir =  app_root_dir + '/config'
        self.app_templates_dir =  app_root_dir + '/templates'
        self.app_scripts_dir =  app_root_dir + '/scripts'

    def run_cmd_ret(self, cmd):
        return self.run_cmd(cmd)[1]

    def run_cmd(self, cmd):
        self.logger.info('run_cmd: %s', cmd)
        ret = commands.getstatusoutput(cmd)
        return ret

    def shell_run(self, cmd, keep_pipeline=True, exit_on_error=True, dry_run=False):
        if datatypes.is_list_or_tuple(cmd):
            cmd = '\n' + ';\n'.join(cmd)

        self.logger.info('shell_run: %s', cmd)
        if dry_run:
            return 0
        ret = 0
        if keep_pipeline:
            # https://stackoverflow.com/questions/10479825/python-subprocess-call-broken-pipe
            ret = subprocess.call(cmd, shell=True, preexec_fn=lambda: signal(SIGPIPE, SIG_DFL))
        else:
            ret = subprocess.call(cmd, shell=True)
        if ret != 0 and self.exit_on_error and exit_on_error:
            sys.exit(ret)
        return ret

    def remove_container(self, name, **kwargs):
        cmd = 'docker rm %s' % (name)
        if kwargs.get('force', False):
            cmd = 'docker rm -f %s' % (name)
        self.shell_run(cmd, exit_on_error=False)

    def stop_container(self, name, **kwargs):
        if 'timeout' not in kwargs:
            kwargs['timeout'] = 300
        cmd = 'docker stop --time %d %s' % (kwargs['timeout'], name)
        self.shell_run(cmd, exit_on_error=False)

    def _ensure_dir_and_write_permision(self):
        file.ensure_dir(self.app_storage_dir)
        file.ensure_dir(self.app_persitent_storage_dir)
        file.ensure_dir(self.app_runtime_storage_dir)
        file.ensure_dir(self.app_logs_dir)

    def _check_lock(self):
        filepath = self.app_runtime_storage_dir + '/locks/' + file.compute_lock_filepath(sys.argv)
        file_lock = file.obtain_lock(filepath)
        if file_lock is None:
            pid = 0
            with open(filepath, 'r') as f:
                pid = f.read()
            self.logger.warning('lock file exists, pid: %s => %s', pid, filepath)
            sys.exit(1)
        else:
            self.file_lock = file_lock

    def template_to(self, template_filename, dst, payload, app_name=None):
        template_payload = {'payload': payload}
        src = self.app_templates_dir_for(app_name) + '/' + template_filename
        template.render_to_file(src, template_payload, dst)
        self.logger.info('template_to: %s => %s', src, dst)

    def read_app_config(self, config_filebasename, app_name=None):
        if '.yml' not in config_filebasename:
            config_filebasename = config_filebasename + '.yml'
        data = utils.load_yaml('%s/%s' % (self.app_config_dir_for(app_name), config_filebasename), {})
        return data

    def app_templates_dir_for(self, app_name=None):
        if app_name is None:
            return self.app_templates_dir
        return '%s/%s/templates' % (self.roles_dir, app_name)

    def app_config_dir_for(self, app_name=None):
        if app_name is None:
            return self.app_config_dir
        return '%s/%s/config' % (self.roles_dir, app_name)

    def get_group_names(self):
        return self._get_hostvar()['group_names']

    def get_configed_ops_scripts_dir(self):
        return self._get_hostvar()['ops_scripts_dir']

    def _get_hostvar(self):
        if self._hostvar is None:
            ansible_kv_config = utils.load_json('/etc/cp-ansible-hostvar.json', {})
            cloud_init_raw_data = utils.load_json('/etc/cp-cloud-init-raw-data.json', {})
            '''
            #### Always respect global_params / app_params from ansible
            1. Create an ECS by cloud-init, global_params from ansible, app_params from cloud-init config.
               We can not read app_params from ansible when we are using cloud-init to deloy,
               so we truncate `/etc/cp-ansible-hostvar.json` when lauch an ECS.
               The global_params and app_params will be the value we desire.
            2. If we deploy by ansible, ansible_kv_config will overwrite cloud_init_raw_data,
               so global_params and app_params both will be the latest. So they are the value we desire too.
            '''
            self._hostvar = array.merge(cloud_init_raw_data, ansible_kv_config)
        return self._hostvar

    def get_app_params(self, key=None, fallback=None):
        app_params = self._get_hostvar().get('app_params', {})
        if key is None:
            return app_params
        return app_params.get(key, fallback)

    def get_global_params(self, key=None, fallback=None):
        global_params = self._get_hostvar().get('global_params', {})
        if key is None:
            return global_params
        return global_params.get(key, fallback)
