#!/usr/bin/env python
#==============================================================================
# Copyright 2012 Amazon.com, Inc. or its affiliates. All Rights Reserved.
#
# Licensed under the Amazon Software License (the "License"). You may not use
# this file except in compliance with the License. A copy of the License is
# located at
#
#       http://aws.amazon.com/asl/
#
# or in the "license" file accompanying this file. This file is distributed on
# an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, express or
# implied. See the License for the specific language governing permissions
# and limitations under the License.
#==============================================================================
import os
import logging
import subprocess

from scli import api_wrapper, config_file, prompt
from scli.constants import AwsCredentialFileDefault, CommandType, DevToolsConfigFile, EbConfigFile, \
                        EbLocalDir, FileErrorConstant, GitIgnoreFile, OSSpecific, \
                        ParameterName, ParameterSource, RdsDefault
from scli.exception import EBSCliException, EBConfigFileNotExistError
from scli.operation.base import OperationBase, OperationResult
from scli.parameter import Parameter
from scli.resources import ConfigFileMessage, ConfigFileErrorMessage, \
                        DevToolsMessage, SaveConfigurationSettingOpMessage, \
                        TerminalMessage, WriteAwsCredentialFileOpMessage
from lib.utility import misc, shell_utils


log = logging.getLogger('cli.op')


def create_eb_local_dir():
    shell_utils.create_directory(os.getcwdu() + os.path.sep + EbLocalDir.Path)    


# Format: ParameterName, from_file function, to_file function
CredentialFileParameters = [
    (ParameterName.AwsAccessKeyId, None, None), 
    (ParameterName.AwsSecretAccessKey, None, None), 
    (ParameterName.RdsMasterPassword, None, None), 
]


class ReadAwsCredentialFileOperation(OperationBase):
    '''
    Try to read AWS credential ID and key from AWS_CREDENTIAL_FILE specified by user 
    or stored in local OS environment variables
    '''  
    _input_parameters = set()
    _output_parameters = set()
    
    def execute(self, parameter_pool):
        # Try to retrieve all credential info from specified file        
        file_param = parameter_pool.get(ParameterName.AwsCredentialFile) \
            if parameter_pool.has(ParameterName.AwsCredentialFile) else None
        if file_param is not None:
            self._try_read_credential_file(parameter_pool, 
                                           file_param.value, 
                                           file_param.source)
        else:
            location = config_file.default_aws_credential_file_location()        
            self._try_read_credential_file(parameter_pool, 
                                           location, 
                                           ParameterSource.ConfigFile)
        
        osenv_location =  os.getenv(AwsCredentialFileDefault.OSVariableName)
        if osenv_location is not None:
            self._try_read_credential_file(parameter_pool, 
                                           osenv_location, 
                                           ParameterSource.OsEnvironment)

        ret_result = OperationResult(self, None, None, None)
        return ret_result


    def _try_read_credential_file(self, parameter_pool, location, source):
        func_matrix = []
        # Loop over default settings
        for name, from_file, _ in CredentialFileParameters:
            if not parameter_pool.has(name):
                func_matrix.append((None, name, from_file))
                
        # Loop over branch environment settings
        branches = parameter_pool.get_value(ParameterName.Branches)
        if branches:
            for branch_name, branch_setting in branches.iteritems():
                if not name in branch_setting:
                    func_matrix.append((branch_name, name, from_file))
                        
        config_file.read_aws_credential_file(location, parameter_pool, 
                                             func_matrix, source, True)


class UpdateAwsCredentialFileOperation(OperationBase):
    '''
    Generate AWS credential file if it is not retrieved from local OS environment variables
    '''  
    _input_parameters = {
                         ParameterName.AwsAccessKeyId, 
                         ParameterName.AwsSecretAccessKey,
                         }
    
    _output_parameters = set()
        
    def execute(self, parameter_pool):
        
        func_matrix = []
        # Loop over default settings
        for name, _, to_file in CredentialFileParameters:
            if not parameter_pool.has(name):
                continue
            elif ParameterSource.is_ahead(ParameterSource.Terminal,\
                                        parameter_pool.get_source(name)):
                continue
            else:
                func_matrix.append((None, name, to_file))

        # Loop over current branch settings
        cur_branch = parameter_pool.get_value(ParameterName.CurrentBranch)
        if cur_branch and ParameterSource.is_ahead(parameter_pool.get_source(ParameterName.Branches),
                                                   ParameterSource.ConfigFile):
            branch_setting = parameter_pool.get_value(ParameterName.Branches, False)[cur_branch]
            for name, _, to_file in CredentialFileParameters:
                if not name in branch_setting:
                    continue
                else:
                    func_matrix.append((cur_branch, name, to_file))
        
        if len(func_matrix) < 1:
            log.info(u'Skipped updating credential file as credentials are not changed.')
            return 

        location = config_file.default_aws_credential_file_location()        
        # Create directory if needed
        try:
            shell_utils.create_directory(config_file.default_aws_credential_file_path())
            config_file.write_aws_credential_file(location, parameter_pool, func_matrix)
        except BaseException as ex:
            log.error(u'Encountered error when creating AWS Credential file at "{0}", because {1}.'.\
                      format(location, ex))
            return
        
        else:
            log.info(WriteAwsCredentialFileOpMessage.Succeed.format(location))
            prompt.result(WriteAwsCredentialFileOpMessage.Succeed.format(location))
            
            parameter_pool.put(Parameter(ParameterName.AwsCredentialFile,
                                         location,
                                         ParameterSource.OperationOutput),
                               True)
            
            ret_result = OperationResult(self, None, None, None)
            return ret_result


class LoadEbConfigFileOperation(OperationBase):
    _input_parameters = set()
    _output_parameters = set()
    
    def execute(self, parameter_pool):
        location = EbLocalDir.Path + os.path.sep + EbConfigFile.Name
        
        try:
            config_file.load_eb_config_file(location, parameter_pool, False)
            if config_file.check_access_permission(location) is False:
                message = ConfigFileErrorMessage.PermissionError.format(EbConfigFile.Name)
                log.info(message)
                prompt.error(message)
           
            #Post processing
            if not parameter_pool.has(ParameterName.RdsSnippetUrl)\
                and parameter_pool.has(ParameterName.Region):
                region = parameter_pool.get_value(ParameterName.Region, False)
                parameter_pool.put(Parameter(ParameterName.RdsSnippetUrl,
                                             RdsDefault.get_snippet_url(region),
                                             ParameterSource.ConfigFile))
            
        except EBConfigFileNotExistError:
            log.error(u'Configuration file "{0}" not exist.'.format(EbConfigFile.Name))
            prompt.error(ConfigFileMessage.CannotFind.format\
                        (EbConfigFile.Name, CommandType.INIT.lower()))
            raise EBSCliException()
            
        except BaseException as ex:
            log.error(u'Encountered error when load configuration file "{0}", becuase "{1}".'.\
                      format(EbConfigFile.Name, ex))
            prompt.error(ConfigFileMessage.CorrectionSuggestion.
                         format(location,CommandType.INIT.lower()))
            raise

        ret_result = OperationResult(self, None, None, None)
        return ret_result

    
class TryLoadEbConfigFileOperation(OperationBase):
    _input_parameters = set()
    _output_parameters = set()
    
    def execute(self, parameter_pool):
        location = EbLocalDir.Path + os.path.sep + EbConfigFile.Name
        
        config_file.load_eb_config_file(location, parameter_pool, True)

        ret_result = OperationResult(self, None, None, None)
        return ret_result



class SaveEbConfigFileOperation(OperationBase):
    _input_parameters = set()
    _output_parameters = set()
    
    def execute(self, parameter_pool):
        create_eb_local_dir()
        
        location = EbLocalDir.Path + os.path.sep + EbConfigFile.Name
        
        config_file.save_eb_config_file(location, parameter_pool, False)
        config_file.set_access_permission(location)
        
        ret_result = OperationResult(self, None, None, None)
        return ret_result
    

class UpdateDevToolsConfigOperation(OperationBase):

    _input_parameters = set()
    _output_parameters = set()
    
    def execute(self, pool):
        
        # Test if git local repo exists
        if not os.path.isdir(os.path.join(os.getcwdu(), DevToolsConfigFile.Path)):
            prompt.error(DevToolsMessage.GitRepoNotExist.format(''))
            return

        error = False
        current_path = os.getcwdu()        
        try:
            log.info(u'Running Dev Tools initialization script.')
            if misc.is_os_windows():
                path = shell_utils.climb_dir_tree(shell_utils.ori_path(), OSSpecific.WindowsClimbUpDepth)
                #TODO: replace current workaround for WindowsModuleScript
                current_path = os.getcwdu()
                script_path = os.path.join(path, OSSpecific.WindowsModuleScriptPath)
                log.debug(u'Changing path to {0}.'.format(script_path))
                os.chdir(script_path)

                log.info(u'Running script "{0}".'.format(OSSpecific.WindowsModuleScriptName))
                shell_utils.call([OSSpecific.WindowsModuleScriptName])
                
                log.debug(u'Changing path to {0}.'.format(current_path))
                os.chdir(current_path)
                
                log.info(u'Running script "{0}".'.format(OSSpecific.WindowsRepoScript))
                fullpath = os.path.join(path, OSSpecific.WindowsRepoScript)
                prompt.error(shell_utils.call([fullpath]))
            else:
                path = shell_utils.climb_dir_tree(shell_utils.ori_path(), OSSpecific.LinuxClimbUpDepth)
                log.info(u'Running script "{0}" at {1}.'.format(OSSpecific.LinuxRepoScript,
                                                                path))
                fullpath = os.path.join(path, OSSpecific.LinuxRepoScript)
                prompt.error(shell_utils.call([fullpath]))
            
            location = DevToolsConfigFile.Path + os.path.sep + DevToolsConfigFile.Name        
            config_file.set_access_permission(location, True)

        except subprocess.CalledProcessError as ex:
            # Git returned with an error code
            log.error(u'Dev Tools initialization script report an error, because "{0}".'.format(ex))
            error = True
            prompt.error(DevToolsMessage.InitError.format(ex.message))
        
        except (OSError, IOError) as ex:
            log.error(u'Failed to call Dev Tools initialization script, because "{0}".'.format(ex))
            # Cannot find or run script
            error = True
            if ex.errno == FileErrorConstant.FileNotFoundErrorCode:
                if fullpath:
                    prompt.error(DevToolsMessage.FileMissingError.format(fullpath))
                else:
                    prompt.error(ex)

        finally:
            if error:            
                prompt.error(DevToolsMessage.ExecutionError.format(DevToolsConfigFile.InitHelpUrl))
        
        ret_result = OperationResult(self, None, None, None)
        return ret_result



class CheckGitIgnoreFileOperation(OperationBase):

    _input_parameters = set()
    
    _output_parameters = set()

    def execute(self, pool):
        location = GitIgnoreFile.Path + os.path.sep + GitIgnoreFile.Name        
        config_file.add_ignore_file(location)
        
        ret_result = OperationResult(self, None, None, None)
        return ret_result        



class SaveConfigurationSettingOperation(OperationBase):
    _input_parameters = {
                         ParameterName.AwsAccessKeyId, 
                         ParameterName.AwsSecretAccessKey,
                         ParameterName.ServiceEndpoint, 
                         ParameterName.ApplicationName,
                         ParameterName.EnvironmentName,
                         ParameterName.OptionSettingFile,
                        }
    
    _output_parameters = set()
    
    def execute(self, parameter_pool):
        create_eb_local_dir()

        app_name = parameter_pool.get_value(ParameterName.ApplicationName)
        env_name = parameter_pool.get_value(ParameterName.EnvironmentName)
        location = parameter_pool.get_value(ParameterName.OptionSettingFile)            

        prompt.action(SaveConfigurationSettingOpMessage.Start.format(env_name))
        
        try:
            option_settings = api_wrapper.retrieve_configuration_settings(parameter_pool,
                                                                          app_name,
                                                                          env_name)
            config_file.save_env_option_setting_file(location, option_settings)
        except Exception as ex:
            # Never fail. Just log event if any exception
            log.info(u'Cannot dump environment option settings before termination, because '.\
                     format(misc.to_unicode(ex)))
            option_settings = None
        else:
            log.info(SaveConfigurationSettingOpMessage.Succeed.format(location))
            prompt.info(SaveConfigurationSettingOpMessage.Succeed.format(location))
                   
        ret_result = OperationResult(self,
                                     None, 
                                     None,
                                     option_settings)
        return ret_result



class RotateOptionsettingFileOperation(OperationBase):
    _input_parameters = {
                         ParameterName.OptionSettingFile,
                        }
    
    _output_parameters = set()
    
    def execute(self, parameter_pool):
        ori_stack = parameter_pool.get_value(ParameterName.OriginalSolutionStack)\
            if parameter_pool.has(ParameterName.OriginalSolutionStack) else None
        stack = parameter_pool.get_value(ParameterName.SolutionStack)\
            if parameter_pool.has(ParameterName.SolutionStack) else None
        
        if ori_stack is None or ori_stack == stack:
            log.info(u'Solution stack is not changed. Keeping current optionsettings file.')
        else:
            log.info(u'Rotate optionsettings file because solution stack is changed.')
            
            # Rotate default option setting file            
            location = parameter_pool.get_value(ParameterName.OptionSettingFile)            
            config_file.rotate_file(location)

            # Rotate branch environment option setting file if any
            branches = parameter_pool.get_value(ParameterName.Branches)\
                if parameter_pool.has(ParameterName.Branches) else None
            if branches:
                for _, branch in branches.iteritems():
                    location = branch[ParameterName.OptionSettingFile]            
                    config_file.rotate_file(location)
        
        ret_result = OperationResult(self, None, None, None)
        return ret_result
       
        
class TryGetCurrentBranchOperation(OperationBase):
    _input_parameters = set()
    
    _output_parameters = {
                          ParameterName.CurrentBranch,
                          }
    
    def execute(self, parameter_pool):
        current_branch, branch_count = shell_utils.get_working_branch(True)
        parameter_pool.put(Parameter(ParameterName.CurrentBranch,
                                     current_branch,
                                     ParameterSource.ConfigFile))
        if current_branch:
            log.info(u'Current working branch is "{0}".'.format(current_branch))
            branches = parameter_pool.get_value(ParameterName.Branches)
            if branches and current_branch in branches.keys():
                log.info(u'Found registered environment for branch "{0}".'.format(current_branch))
                for key, value in branches[current_branch].iteritems():
                    parameter_pool.put(Parameter(key, value, ParameterSource.ConfigFile))
            else:
                if branch_count == 1:
                    log.info(u'Only one unregistered branch found. Using default settings.')
                    pass
                else:
                    msg = TerminalMessage.FallBackToDefaultBranch.format(current_branch)
                    log.error(msg)
                    prompt.error(msg)
        else:
            # local repository does not have more than one branch, using default
            pass

