# -*- coding: utf-8 -*-
from __future__ import unicode_literals

import io
import logging
import os
import sys
import subprocess

from os.path import expanduser
from contextlib import contextmanager
from io import StringIO
from dotenv import dotenv_values

from .compat import PY2, StringIO, to_env
from .version import __version__

logger = logging.getLogger(__name__)

def with_warn_for_invalid_lines(mappings):
    # type: (Iterator[Binding]) -> Iterator[Binding]
    for mapping in mappings:
        if mapping.error:
            logger.warning(
                "cloudenv could not parse statement starting at line %s",
                mapping.original.line,
            )
        yield mapping


class Cloudenv():

    def __init__(self, bearer_file, secret_key_file, override=True):
        self.bearer_file = bearer_file
        self.secret_key_file = secret_key_file
        self._dict = None
        self.api_host = "https://app.cloudenv.com"
        self.read_path = "/api/v1/envs"
        self.override = override

    def dict(self):
        if self._dict:
            return self._dict

        if 'PYTHON_ENV' in os.environ.keys():
            self._dict = self.parse(os.getenv('PYTHON_ENV'))
            self._dict.update(self.parse('default'))
        else:
            self._dict = self.parse('default')

        return self._dict

    def parse(self, environment):
        try:
            if self.is_valid_cloudenv_app():
                stdout = subprocess.getoutput('curl -s -H "Authorization: Bearer {0}" "{1}{2}?name={3}&environment={4}&version={5}&lang=python" | openssl enc -a -aes-256-cbc -md sha512 -d -pass pass:"{6}" 2> /dev/null'.format(self.bearer(), self.api_host, self.read_path, self.app_name(), environment, __version__, self.secret_key()))
                filelike = StringIO(stdout)
                filelike.seek(0)
                return dotenv_values(stream=filelike)
            else:
                return {}
        except UnicodeDecodeError:
            return {}

    def bearer(self):
        return os.getenv('CLOUDENV_BEARER_TOKEN') or open(self.bearer_file, 'r').read().strip()

    def secret_key(self):
        return os.getenv('CLOUDENV_APP_SECRET_KEY') or open(self.secret_key_file, 'r').read().split('\n')[1].split()[1]

    def app_name(self):
        return os.getenv('CLOUDENV_APP_SLUG') or open(self.secret_key_file, 'r').read().split('\n')[0].split()[1]

    def is_valid_cloudenv_app(self):
        return os.path.exists(self.secret_key_file)

    def set_as_environment_variables(self):
        """
        Load the current cloudenv as system environemt variable.
        """
        for k, v in self.dict().items():
            if k in os.environ and not self.override:
                continue
            if v is not None:
                os.environ[to_env(k)] = to_env(v)

        return True

    def get(self, key):
        data = self.dict()

        if key in data:
            return data[key]

        if self.verbose:
            logger.warning("Key %s not found in %s.", key, self.cloudenv_path)

        return None

def _walk_to_root(path):
    """
    Yield directories starting from the given directory up to the root
    """
    if not os.path.exists(path):
        raise IOError('Starting path not found')

    if os.path.isfile(path):
        path = os.path.dirname(path)

    last_dir = None
    current_dir = os.path.abspath(path)
    while last_dir != current_dir:
        yield current_dir
        parent_dir = os.path.abspath(os.path.join(current_dir, os.path.pardir))
        last_dir, current_dir = current_dir, parent_dir


def find_cloudenv_bearer(filename='.cloudenvrc', raise_error_if_not_found=True):
    """
    Search in increasingly higher folders for the given file

    Returns path to the file if found, or an empty string otherwise
    """

    # will work for .py files
    frame = sys._getframe()
    # find first frame that is outside of this file
    if PY2 and not __file__.endswith('.py'):
        # in Python2 __file__ extension could be .pyc or .pyo (this doesn't account
        # for edge case of Python compiled for non-standard extension)
        current_file = __file__.rsplit('.', 1)[0] + '.py'
    else:
        current_file = __file__

    while frame.f_code.co_filename == current_file:
        assert frame.f_back is not None
        frame = frame.f_back
    frame_filename = frame.f_code.co_filename
    path = os.path.dirname(os.path.abspath(frame_filename))

    for dirname in _walk_to_root(path):
        check_path = os.path.join(dirname, filename)
        if os.path.isfile(check_path):
            return check_path

    for dirname in _walk_to_root(expanduser("~")):
        check_path = os.path.join(dirname, filename)
        if os.path.isfile(check_path):
            return check_path

    if raise_error_if_not_found:
        raise IOError('Cloudenv bearer token file not found (should be at .cloudenvrc)')

    return ''

def find_cloudenv_secret_key(filename='.cloudenv-secret-key', raise_error_if_not_found=True):
    """
    Search in increasingly higher folders for the given file

    Returns path to the file if found, or an empty string otherwise
    """

    # will work for .py files
    frame = sys._getframe()
    # find first frame that is outside of this file
    if PY2 and not __file__.endswith('.py'):
        # in Python2 __file__ extension could be .pyc or .pyo (this doesn't account
        # for edge case of Python compiled for non-standard extension)
        current_file = __file__.rsplit('.', 1)[0] + '.py'
    else:
        current_file = __file__

    while frame.f_code.co_filename == current_file:
        assert frame.f_back is not None
        frame = frame.f_back
    frame_filename = frame.f_code.co_filename
    path = os.path.dirname(os.path.abspath(frame_filename))

    for dirname in _walk_to_root(path):
        check_path = os.path.join(dirname, filename)
        if os.path.isfile(check_path):
            return check_path

    if raise_error_if_not_found:
        raise IOError('Cloudenv secret key file not found (should be at .cloudenv-secret-key)')

    return ''


def load_cloudenv(cloudenv_bearer=None, cloudenv_secret_key=None, override=False):
    """Get your cloudenv variables and then load all the variables found as environment variables.

    - *cloudenv_bearer*: absolute or relative path to .env file.
    - *cloudenv_secret_key*: absolute or relative path to .env file.
    - *override*: where to override the system environment variables with the variables in `.env` file.
                  Defaults to `False`.
    """
    bearer_file = cloudenv_bearer or os.getenv('CLOUDENV_BEARER_TOKEN') or find_cloudenv_bearer()
    secret_key_file = cloudenv_secret_key or find_cloudenv_secret_key()
    cloudenv = Cloudenv(bearer_file, secret_key_file, override=override)
    return cloudenv.set_as_environment_variables()


def cloudenv_values(cloudenv_bearer=None, cloudenv_secret_key=None):
    bearer_file = cloudenv_bearer or os.getenv('CLOUDENV_BEARER_TOKEN') or find_cloudenv_bearer()
    secret_key = cloudenv_secret_key or find_cloudenv_secret_key()
    cloudenv = Cloudenv(bearer_file, secret_key_file, override=True)
    return cloudenv.dict()

# -*- coding: utf-8 -*-
from __future__ import unicode_literals

import io
import logging
import os
import sys
import subprocess

from os.path import expanduser
from contextlib import contextmanager
from io import StringIO
from dotenv import dotenv_values

from .compat import PY2, StringIO, to_env
from .version import __version__

logger = logging.getLogger(__name__)

def with_warn_for_invalid_lines(mappings):
    # type: (Iterator[Binding]) -> Iterator[Binding]
    for mapping in mappings:
        if mapping.error:
            logger.warning(
                "cloudenv could not parse statement starting at line %s",
                mapping.original.line,
            )
        yield mapping


class Cloudenv():

    def __init__(self, bearer_file, secret_key_file, override=True):
        self.bearer_file = bearer_file
        self.secret_key_file = secret_key_file
        self._dict = None
        self.api_host = "https://app.cloudenv.com"
        self.read_path = "/api/v1/envs"
        self.override = override

    def dict(self):
        if self._dict:
            return self._dict

        if 'PYTHON_ENV' in os.environ.keys():
            self._dict = self.parse(os.getenv('PYTHON_ENV'))
            self._dict.update(self.parse('default'))
        else:
            self._dict = self.parse('default')

        return self._dict

    def parse(self, environment):
        try:
            if self.is_valid_cloudenv_app():
                stdout = subprocess.getoutput('curl -s -H "Authorization: Bearer {0}" "{1}{2}?name={3}&environment={4}&version={5}&lang=python" | openssl enc -a -aes-256-cbc -md sha512 -d -pass pass:"{6}" 2> /dev/null'.format(self.bearer(), self.api_host, self.read_path, self.app_name(), environment, __version__, self.secret_key()))
                filelike = StringIO(stdout)
                filelike.seek(0)
                return dotenv_values(stream=filelike)
            else:
                return {}
        except UnicodeDecodeError:
            return {}

    def bearer(self):
        return os.getenv('CLOUDENV_BEARER_TOKEN') or open(self.bearer_file, 'r').read().strip()

    def secret_key(self):
        return os.getenv('CLOUDENV_APP_SECRET_KEY') or open(self.secret_key_file, 'r').read().split('\n')[1].split()[1]

    def app_name(self):
        return os.getenv('CLOUDENV_APP_SLUG') or open(self.secret_key_file, 'r').read().split('\n')[0].split()[1]

    def is_valid_cloudenv_app(self):
        return os.path.exists(self.secret_key_file)

    def set_as_environment_variables(self):
        """
        Load the current cloudenv as system environemt variable.
        """
        for k, v in self.dict().items():
            if k in os.environ and not self.override:
                continue
            if v is not None:
                os.environ[to_env(k)] = to_env(v)

        return True

    def get(self, key):
        data = self.dict()

        if key in data:
            return data[key]

        if self.verbose:
            logger.warning("Key %s not found in %s.", key, self.cloudenv_path)

        return None

def _walk_to_root(path):
    """
    Yield directories starting from the given directory up to the root
    """
    if not os.path.exists(path):
        raise IOError('Starting path not found')

    if os.path.isfile(path):
        path = os.path.dirname(path)

    last_dir = None
    current_dir = os.path.abspath(path)
    while last_dir != current_dir:
        yield current_dir
        parent_dir = os.path.abspath(os.path.join(current_dir, os.path.pardir))
        last_dir, current_dir = current_dir, parent_dir


def find_cloudenv_bearer(filename='.cloudenvrc', raise_error_if_not_found=True):
    """
    Search in increasingly higher folders for the given file

    Returns path to the file if found, or an empty string otherwise
    """

    # will work for .py files
    frame = sys._getframe()
    # find first frame that is outside of this file
    if PY2 and not __file__.endswith('.py'):
        # in Python2 __file__ extension could be .pyc or .pyo (this doesn't account
        # for edge case of Python compiled for non-standard extension)
        current_file = __file__.rsplit('.', 1)[0] + '.py'
    else:
        current_file = __file__

    while frame.f_code.co_filename == current_file:
        assert frame.f_back is not None
        frame = frame.f_back
    frame_filename = frame.f_code.co_filename
    path = os.path.dirname(os.path.abspath(frame_filename))

    for dirname in _walk_to_root(path):
        check_path = os.path.join(dirname, filename)
        if os.path.isfile(check_path):
            return check_path

    for dirname in _walk_to_root(expanduser("~")):
        check_path = os.path.join(dirname, filename)
        if os.path.isfile(check_path):
            return check_path

    if raise_error_if_not_found:
        raise IOError('Cloudenv bearer token file not found (should be at .cloudenvrc)')

    return ''

def find_cloudenv_secret_key(filename='.cloudenv-secret-key', raise_error_if_not_found=True):
    """
    Search in increasingly higher folders for the given file

    Returns path to the file if found, or an empty string otherwise
    """

    # will work for .py files
    frame = sys._getframe()
    # find first frame that is outside of this file
    if PY2 and not __file__.endswith('.py'):
        # in Python2 __file__ extension could be .pyc or .pyo (this doesn't account
        # for edge case of Python compiled for non-standard extension)
        current_file = __file__.rsplit('.', 1)[0] + '.py'
    else:
        current_file = __file__

    while frame.f_code.co_filename == current_file:
        assert frame.f_back is not None
        frame = frame.f_back
    frame_filename = frame.f_code.co_filename
    path = os.path.dirname(os.path.abspath(frame_filename))

    for dirname in _walk_to_root(path):
        check_path = os.path.join(dirname, filename)
        if os.path.isfile(check_path):
            return check_path

    if raise_error_if_not_found:
        raise IOError('Cloudenv secret key file not found (should be at .cloudenv-secret-key)')

    return ''


def load_cloudenv(cloudenv_bearer=None, cloudenv_secret_key=None, override=False):
    """Get your cloudenv variables and then load all the variables found as environment variables.

    - *cloudenv_bearer*: absolute or relative path to .env file.
    - *cloudenv_secret_key*: absolute or relative path to .env file.
    - *override*: where to override the system environment variables with the variables in `.env` file.
                  Defaults to `False`.
    """
    bearer_file = cloudenv_bearer or os.getenv('CLOUDENV_BEARER_TOKEN') or find_cloudenv_bearer()
    secret_key_file = cloudenv_secret_key or find_cloudenv_secret_key()
    cloudenv = Cloudenv(bearer_file, secret_key_file, override=override)
    return cloudenv.set_as_environment_variables()


def cloudenv_values(cloudenv_bearer=None, cloudenv_secret_key=None):
    bearer_file = cloudenv_bearer or os.getenv('CLOUDENV_BEARER_TOKEN') or find_cloudenv_bearer()
    secret_key = cloudenv_secret_key or find_cloudenv_secret_key()
    cloudenv = Cloudenv(bearer_file, secret_key_file, override=True)
    return cloudenv.dict()

# -*- coding: utf-8 -*- # type: (Iterator[Binding]) -> Iterator[Binding] Load the current cloudenv as system environemt variable. Yield directories starting from the given directory up to the root Search in increasingly higher folders for the given file      Returns path to the file if found, or an empty string otherwise # will work for .py files # find first frame that is outside of this file # in Python2 __file__ extension could be .pyc or .pyo (this doesn't account # for edge case of Python compiled for non-standard extension) Search in increasingly higher folders for the given file      Returns path to the file if found, or an empty string otherwise # will work for .py files # find first frame that is outside of this file # in Python2 __file__ extension could be .pyc or .pyo (this doesn't account # for edge case of Python compiled for non-standard extension) Get your cloudenv variables and then load all the variables found as environment variables.      - *cloudenv_bearer*: absolute or relative path to .env file.     - *cloudenv_secret_key*: absolute or relative path to .env file.     - *override*: where to override the system environment variables with the variables in `.env` file.                   Defaults to `False`.