import warnings

try:
    from flask.config import Config

    flask_installed = True
except ImportError:  # pragma: no cover
    flask_installed = False
    Config = object


import dynaconf
from importlib import import_module


class FlaskDynaconf(object):
    """The arguments are.
    app = The created app
    dynaconf_args = Extra args to be passed to Dynaconf (validator for example)

    All other values are stored as config vars specially::

        ENVVAR_PREFIX_FOR_DYNACONF = env prefix for your envvars to be loaded
                            example:
                                if you set to `MYSITE` then
                                export MYSITE_SQL_PORT='@int 5445'

                            with that exported to env you access using:
                                app.config.SQL_PORT
                                app.config.get('SQL_PORT')
                                app.config.get('sql_port')
                                # get is case insensitive
                                app.config['SQL_PORT']

                            Dynaconf uses `@int, @bool, @float, @json` to cast
                            env vars

        SETTINGS_FILE_FOR_DYNACONF = The name of the module or file to use as
                                    default to load settings. If nothing is
                                    passed it will be `settings.*` or value
                                    found in `ENVVAR_FOR_DYNACONF`
                                    Dynaconf supports
                                    .py, .yml, .toml, ini, json

    ATTENTION: Take a look at `settings.yml` and `.secrets.yml` to know the
            required settings format.

    Settings load order in Dynaconf:

    - Load all defaults and Flask defaults
    - Load all passed variables when applying FlaskDynaconf
    - Update with data in settings files
    - Update with data in environment vars `ENVVAR_FOR_DYNACONF_`


    TOML files are very useful to have `envd` settings, lets say,
    `production` and `development`.

    You can also achieve the same using multiple `.py` files naming as
    `settings.py`, `production_settings.py` and `development_settings.py`
    (see examples/validator)

    Example::

        app = Flask(__name__)
        FlaskDynaconf(
            app,
            ENV='MYSITE',
            SETTINGS_FILE='settings.yml',
            EXTRA_VALUE='You can add aditional config vars here'
        )

    Take a look at examples/flask in Dynaconf repository

    """

    def __init__(
        self,
        app=None,
        instance_relative_config=False,
        dynaconf_instance=None,
        extensions_list=False,
        **kwargs,
    ):
        """kwargs holds initial dynaconf configuration"""
        if not flask_installed:  # pragma: no cover
            raise RuntimeError(
                "To use this extension Flask must be installed "
                "install it with: pip install flask"
            )
        self.kwargs = kwargs

        kwargs.setdefault("ENVVAR_PREFIX", "FLASK")
        env_prefix = f"{kwargs['ENVVAR_PREFIX']}_ENV"  # FLASK_ENV
        kwargs.setdefault("ENV_SWITCHER", env_prefix)
        kwargs.setdefault("ENVIRONMENTS", True)
        kwargs.setdefault("load_dotenv", True)
        kwargs.setdefault(
            "default_settings_paths", dynaconf.DEFAULT_SETTINGS_FILES
        )

        self.dynaconf_instance = dynaconf_instance
        self.instance_relative_config = instance_relative_config
        self.extensions_list = extensions_list
        if app:
            self.init_app(app, **kwargs)

    def init_app(self, app, **kwargs):
        """kwargs holds initial dynaconf configuration"""
        self.kwargs.update(kwargs)
        self.settings = self.dynaconf_instance or dynaconf.LazySettings(
            **self.kwargs
        )
        dynaconf.settings = self.settings  # rebind customized settings
        app.config = self.make_config(app)
        app.dynaconf = self.settings

        if self.extensions_list:
            if not isinstance(self.extensions_list, str):
                self.extensions_list = "EXTENSIONS"
            app.config.load_extensions(self.extensions_list)

    def make_config(self, app):
        root_path = app.root_path
        if self.instance_relative_config:  # pragma: no cover
            root_path = app.instance_path
        if self.dynaconf_instance:
            self.settings.update(self.kwargs)
        return DynaconfConfig(
            root_path=root_path,
            defaults=app.config,
            _settings=self.settings,
            _app=app,
        )


class DynaconfConfig(Config):
    """
    Settings load order in Dynaconf:

    - Load all defaults and Flask defaults
    - Load all passed variables when applying FlaskDynaconf
    - Update with data in settings files
    - Update with data in environmente vars `ENV_FOR_DYNACONF_`
    """

    def __init__(self, _settings, _app, *args, **kwargs):
        """perform the initial load"""
        super(DynaconfConfig, self).__init__(*args, **kwargs)
        Config.update(self, _settings.store)
        self._settings = _settings
        self._app = _app

    def __getitem__(self, key):
        """
        Flask templates always expects a None when key is not found in config
        """
        return self.get(key)

    def __setitem__(self, key, value):
        """
        Allows app.config['key'] = 'foo'
        """
        return self._settings.__setitem__(key, value)

    def __getattr__(self, name):
        """
        First try to get value from dynaconf then from Flask
        """
        try:
            return getattr(self._settings, name)
        except AttributeError:
            return self[name]

    def __call__(self, name, *args, **kwargs):
        return self.get(name, *args, **kwargs)

    def get(self, key, default=None):
        """Gets config from dynaconf variables
        if variables does not exists in dynaconf try getting from
        `app.config` to support runtime settings."""
        return self._settings.get(key, Config.get(self, key, default))

    def load_extensions(self, key="EXTENSIONS", app=None):
        """Loads flask extensions dynamically."""
        app = app or self._app
        extensions = app.config.get(key)
        if not extensions:
            warnings.warn(
                f"Settings is missing {key} to load Flask Extensions",
                RuntimeWarning,
            )
            return

        for extension in app.config[key]:
            # Split data in form `extension.path:factory_function`
            module_name, factory = extension.split(":")
            # Dynamically import extension module.
            ext = import_module(module_name)
            # Invoke factory passing app.
            getattr(ext, factory)(app)
