from __future__ import unicode_literals
from collections import OrderedDict
from distutils.version import LooseVersion
import os

from github3 import login
import hiyapyco
import raven

import cumulusci
from cumulusci.core.config import BaseTaskFlowConfig
from cumulusci.core.exceptions import ConfigError
from cumulusci.core.exceptions import DependencyResolutionError
from cumulusci.core.exceptions import KeychainNotFound
from cumulusci.core.exceptions import ServiceNotConfigured
from cumulusci.core.exceptions import ServiceNotValid


class BaseProjectConfig(BaseTaskFlowConfig):
    """ Base class for a project's configuration which extends the global config """

    search_path = ['config']

    def __init__(self, global_config_obj, config=None):
        self.global_config_obj = global_config_obj
        self.keychain = None
        if not config:
            config = {}
        super(BaseProjectConfig, self).__init__(config=config)

    @property
    def config_global_local(self):
        return self.global_config_obj.config_global_local

    @property
    def config_global(self):
        return self.global_config_obj.config_global

    @property
    def repo_info(self):
        if hasattr(self, '_repo_info'):
            return self._repo_info

        # Detect if we are running in a CI environment and get repo info
        # from env vars for the enviornment instead of .git files
        info = {
            'ci': None
        }

        # Make sure that the CUMULUSCI_AUTO_DETECT environment variable is 
        # set before trying to auto-detect anything from the environment
        if not os.environ.get('CUMULUSCI_AUTO_DETECT'):
            self._repo_info = info
            return self._repo_info

        # Heroku CI
        heroku_ci = os.environ.get('HEROKU_TEST_RUN_ID')
        if heroku_ci:
            info = {
                'branch': os.environ.get('HEROKU_TEST_RUN_BRANCH'),
                'commit': os.environ.get('HEROKU_TEST_RUN_COMMIT_VERSION'),
                'ci': 'heroku',
                'root': '/app',
            }

        # Other CI environment implementations can be implemented here...

        # Apply CUMULUSCI_REPO_* environment variables last so they can
        # override and fill in missing values from the CI environment
        repo_branch = os.environ.get('CUMULUSCI_REPO_BRANCH')
        if repo_branch:
            if repo_branch != info.get('branch'):
                self.logger.info(
                    'CUMULUSCI_REPO_BRANCH found, using its value as the branch'
                )
            info['branch'] = repo_branch
        repo_commit = os.environ.get('CUMULUSCI_REPO_COMMIT')
        if repo_commit:
            if repo_commit != info.get('commit'):
                self.logger.info(
                    'CUMULUSCI_REPO_COMMIT found, using its value as the commit'
                )
            info['commit'] = repo_commit
        repo_root = os.environ.get('CUMULUSCI_REPO_ROOT')
        if repo_root:
            if repo_root != info.get('root'):
                self.logger.info(
                    'CUMULUSCI_REPO_ROOT found, using its value as the repo root'
                )
            info['root'] = repo_root
        repo_url = os.environ.get('CUMULUSCI_REPO_URL')
        if repo_url:
            if repo_url != info.get('url'):
                self.logger.info(
                    'CUMULUSCI_REPO_URL found, using its value as the repo url, owner, and name'
                )
            url_info = self._split_repo_url(repo_url)
            info.update(url_info)

        # If running in a CI environment, make sure we have all the needed
        # git info or throw a ConfigError
        if info['ci']:
            validate = OrderedDict((
                # <key>, <env var to manually override>
                ('branch', 'CUMULUSCI_REPO_BRANCH'),
                ('commit', 'CUMULUSCI_REPO_COMMIT'),
                ('name', 'CUMULUSCI_REPO_URL'),
                ('owner', 'CUMULUSCI_REPO_URL'),
                ('root', 'CUMULUSCI_REPO_ROOT'),
                ('url', 'CUMULUSCI_REPO_URL'),
            ))
            for key, env_var in list(validate.items()):
                if key not in info or not info[key]:
                    message = 'Detected CI on {} but could not determine the repo {}'.format(
                        info['ci'],
                        key,
                    )
                    if env_var:
                        message += '. You can manually pass in the {} with'.format(key)
                        message += ' with the {} environment variable.'.format(env_var)
                    raise ConfigError(message)

        # Log any overrides detected through the environment as a warning
        if len(info) > 1:
            self.logger.info('')
            self.logger.warn(
                'Using environment variables to override repo info:'
            )
            keys = list(info.keys())
            keys.sort()
            for key in keys:
                self.logger.warn(
                    '  {}: {}'.format(key, info[key])
                )
            self.logger.info('')

        self._repo_info = info
        return self._repo_info

    def _split_repo_url(self, url):
        url_parts = url.split('/')
        name = url_parts[-1]
        owner = url_parts[-2]
        if name.endswith('.git'):
            name = name[:-4]
        git_info = {
            'url': url,
            'owner': owner,
            'name': name,
        }
        return git_info

    @property
    def repo_root(self):
        path = self.repo_info.get('root')
        if path:
            return path

        path = os.path.splitdrive(os.getcwd())[1]
        while True:
            if os.path.isdir(os.path.join(path, '.git')):
                return path
            head, tail = os.path.split(path)
            if not tail:
                # reached the root
                break
            path = head

    @property
    def repo_name(self):
        name = self.repo_info.get('name')
        if name:
            return name

        if not self.repo_root:
            return

        in_remote_origin = False
        with open(os.path.join(self.repo_root, '.git', 'config'), 'r') as f:
            for line in f:
                line = line.strip()
                if line == '[remote "origin"]':
                    in_remote_origin = True
                    continue
                if in_remote_origin and line.find('url =') != -1:
                    return self._split_repo_url(line)['name']

    @property
    def repo_url(self):
        url = self.repo_info.get('url')
        if url:
            return url

        if not self.repo_root:
            return

        git_config_file = os.path.join(self.repo_root, '.git', 'config')
        with open(git_config_file, 'r') as f:
            in_remote_origin = False
            for line in f:
                line = line.strip()
                if line == '[remote "origin"]':
                    in_remote_origin = True
                    continue
                if in_remote_origin and 'url = ' in line:
                    return line[7:]

    @property
    def repo_owner(self):
        owner = self.repo_info.get('owner')
        if owner:
            return owner

        if not self.repo_root:
            return

        in_remote_origin = False
        with open(os.path.join(self.repo_root, '.git', 'config'), 'r') as f:
            for line in f:
                line = line.strip()
                if line == '[remote "origin"]':
                    in_remote_origin = True
                    continue
                if in_remote_origin and line.find('url =') != -1:
                    line_parts = line.split('/')
                    return line_parts[-2].split(':')[-1]

    @property
    def repo_branch(self):
        branch = self.repo_info.get('branch')
        if branch:
            return branch

        if not self.repo_root:
            return

        with open(os.path.join(self.repo_root, '.git', 'HEAD'), 'r') as f:
            branch_ref = f.read().strip()
        if branch_ref.startswith('ref: '):
            return '/'.join(branch_ref[5:].split('/')[2:])

    @property
    def repo_commit(self):
        commit = self.repo_info.get('commit')
        if commit:
            return commit

        if not self.repo_root:
            return

        branch = self.repo_branch
        if not branch:
            return

        join_args = [self.repo_root, '.git', 'refs', 'heads']
        join_args.extend(branch.split('/'))
        commit_file = os.path.join(*join_args)

        commit_sha = None
        if os.path.isfile(commit_file):
            with open(commit_file, 'r') as f:
                commit_sha = f.read().strip()
        else: