#!/usr/bin/env python
# -*- coding: utf-8 -*-
# Copyright (c) 2016-2019 Satpy developers
#
# This file is part of satpy.
#
# satpy is free software: you can redistribute it and/or modify it under the
# terms of the GNU General Public License as published by the Free Software
# Foundation, either version 3 of the License, or (at your option) any later
# version.
#
# satpy is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
# A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License along with
# satpy.  If not, see <http://www.gnu.org/licenses/>.
"""Satpy Configuration directory and file handling."""
from __future__ import annotations

import ast
import glob
import logging
import os
import sys
import tempfile
from collections import OrderedDict
from importlib.metadata import EntryPoint, entry_points
from importlib.resources import files as impr_files
from typing import Iterable

from donfig import Config
from platformdirs import AppDirs

from satpy._compat import cache

LOG = logging.getLogger(__name__)

BASE_PATH = os.path.dirname(os.path.realpath(__file__))
# FIXME: Use package_resources?
PACKAGE_CONFIG_PATH = os.path.join(BASE_PATH, "etc")

_satpy_dirs = AppDirs(appname="satpy", appauthor="pytroll")
_CONFIG_DEFAULTS = {
    "tmp_dir": tempfile.gettempdir(),
    "cache_dir": _satpy_dirs.user_cache_dir,
    "cache_lonlats": False,
    "cache_sensor_angles": False,
    "config_path": [],
    "data_dir": _satpy_dirs.user_data_dir,
    "demo_data_dir": ".",
    "download_aux": True,
    "sensor_angles_position_preference": "actual",
    "readers": {
        "clip_negative_radiances": False,
    },
}

# Satpy main configuration object
# See https://donfig.readthedocs.io/en/latest/configuration.html
# for more information.
#
# Configuration values will be loaded from files at:
# 1. The builtin package satpy.yaml (not present currently)
# 2. $SATPY_ROOT_CONFIG (default: /etc/satpy/satpy.yaml)
# 3. <python-env-prefix>/etc/satpy/satpy.yaml
# 4. ~/.config/satpy/satpy.yaml
# 5. ~/.satpy/satpy.yaml
# 6. $SATPY_CONFIG_PATH/satpy.yaml if present (colon separated)
_CONFIG_PATHS = [
    os.path.join(PACKAGE_CONFIG_PATH, "satpy.yaml"),
    os.getenv("SATPY_ROOT_CONFIG", os.path.join("/etc", "satpy", "satpy.yaml")),
    os.path.join(sys.prefix, "etc", "satpy", "satpy.yaml"),
    os.path.join(_satpy_dirs.user_config_dir, "satpy.yaml"),
    os.path.join(os.path.expanduser("~"), ".satpy", "satpy.yaml"),
]
# The above files can also be directories. If directories all files
# with `.yaml`., `.yml`, or `.json` extensions will be used.

_ppp_config_dir = os.getenv("PPP_CONFIG_DIR", None)
_satpy_config_path = os.getenv("SATPY_CONFIG_PATH", None)

if _ppp_config_dir is not None and _satpy_config_path is None:
    LOG.warning("'PPP_CONFIG_DIR' is deprecated. Please use 'SATPY_CONFIG_PATH' instead.")
    _satpy_config_path = _ppp_config_dir

if _satpy_config_path is not None:
    if _satpy_config_path.startswith("["):
        # 'SATPY_CONFIG_PATH' is set by previous satpy config as a reprsentation of a 'list'
        # need to use 'ast.literal_eval' to parse the string back to a list
        _satpy_config_path_list = ast.literal_eval(_satpy_config_path)
    else:
        # colon-separated are ordered by custom -> builtins
        # i.e. last-applied/highest priority to first-applied/lowest priority
        _satpy_config_path_list = _satpy_config_path.split(os.pathsep)

    os.environ["SATPY_CONFIG_PATH"] = repr(_satpy_config_path_list)
    for config_dir in _satpy_config_path_list:
        _CONFIG_PATHS.append(os.path.join(config_dir, "satpy.yaml"))

_ancpath = os.getenv("SATPY_ANCPATH", None)
_data_dir = os.getenv("SATPY_DATA_DIR", None)
if _ancpath is not None and _data_dir is None:
    LOG.warning("'SATPY_ANCPATH' is deprecated. Please use 'SATPY_DATA_DIR' instead.")
    os.environ["SATPY_DATA_DIR"] = _ancpath

config = Config("satpy", defaults=[_CONFIG_DEFAULTS], paths=_CONFIG_PATHS)


def get_config_path_safe():
    """Get 'config_path' and check for proper 'list' type."""
    config_path = config.get("config_path")
    if not isinstance(config_path, list):
        raise ValueError("Satpy config option 'config_path' must be a "
                         "list, not '{}'".format(type(config_path)))
    return config_path


def get_entry_points_config_dirs(group_name: str, include_config_path: bool = True) -> list[str]:
    """Get the config directories for all entry points of given name."""
    dirs: list[str] = []
    for entry_point in cached_entry_point(group_name):
        module = _entry_point_module(entry_point)
        new_dir = str(impr_files(module) / "etc")
        if not dirs or dirs[-1] != new_dir:
            dirs.append(new_dir)
    if include_config_path:
        dirs.extend(config.get("config_path")[::-1])
    return dirs


@cache
def cached_entry_point(group_name: str) -> Iterable[EntryPoint]:
    """Return entry_point for specified ``group``.

    This is a dummy proxy to allow caching and provide compatibility between
    versions of Python and importlib_metadata.

    """
    try:
        # mypy in pre-commit currently checks for Python 3.8 compatibility
        # this line is for Python 3.10+ so it will fail checks
        return entry_points(group=group_name)  # type: ignore
    except TypeError:
        # Python <3.10
        entry_points_list = entry_points()
        return entry_points_list.get(group_name, [])


def _entry_point_module(entry_point):
    try:
        return entry_point.module
    except AttributeError:
        # Python 3.8
        return entry_point.value.split(":")[0].strip()


def config_search_paths(filename, search_dirs=None, **kwargs):
    """Get series of configuration base paths where Satpy configs are located."""
    if search_dirs is None:
        search_dirs = get_config_path_safe()[::-1]

    paths = [filename, os.path.basename(filename)]
    paths += [os.path.join(search_dir, filename) for search_dir in search_dirs]
    paths += [os.path.join(PACKAGE_CONFIG_PATH, filename)]
    paths = [os.path.abspath(path) for path in paths]

    if kwargs.get("check_exists", True):
        paths = [x for x in paths if os.path.isfile(x)]

    paths = list(OrderedDict.fromkeys(paths))
    # flip the order of the list so builtins are loaded first
    return paths[::-1]


def glob_config(pattern, search_dirs=None):
    """Return glob results for all possible configuration locations.

    Note: This method does not check the configuration "base" directory if the pattern includes a subdirectory.
          This is done for performance since this is usually used to find *all* configs for a certain component.
    """
    patterns = config_search_paths(pattern, search_dirs=search_dirs,
                                   check_exists=False)
    for pattern_fn in patterns:
        for path in glob.iglob(pattern_fn):
            yield path


def get_config_path(filename):
    """Get the path to the highest priority version of a config file."""
    paths = config_search_paths(filename)
    for path in paths[::-1]:
        if os.path.exists(path):
            return path
    raise FileNotFoundError("Could not find file in configuration path: "
                            "'{}'".format(filename))
