# Copyright (C) 2020-2022 HERE Global B.V. and its affiliate(s).
# All rights reserved.
#
# This software and other materials contain proprietary information
# controlled by HERE and are protected by applicable copyright legislation.
# Any use and utilization of this software and other materials and
# disclosure to any third parties is conditional upon having a separate
# agreement with HERE for the access, use, utilization or disclosure of this
# software. In the absence of such agreement, the use of the software is not
# allowed.

"""
This module provides a ``ApplicationConfig`` class to be used for configuration management.

A config object can be created from a ``application_config.conf`` file in the package, or from a
custom config file path provided.
"""
import os
from dataclasses import dataclass
from os.path import expanduser, expandvars
from pathlib import Path

from here.platform.exceptions import ConfigException
from here.platform.utils.properties_helper import PropertiesDict

DEFAULT_APPLICATION_CONFIG_PATH = "~/.here/application_config.conf"


@dataclass
class _Constants:
    # field = (field_name, default_value)
    retry_max_time = ("RETRY_MAX_TIME", 195)
    connect_retry_max_time = ("CONNECT_RETRY_MAX_TIME", 10)
    connect_timeout = ("CONNECT_TIMEOUT", 3.05)
    read_timeout = ("READ_TIMEOUT", 60)
    polling_wait = ("POLLING_WAIT", 5.0)
    max_object_store_upload_part_size = (
        "MAX_OBJECT_STORE_UPLOAD_PART_SIZE_MB",
        96,
    )  # 96mb default maximum size for write_object


_constants = _Constants()

_ensureValue = (
    lambda config, field_const: config[field_const[0]]
    if field_const[0] in config
    else field_const[1]
)


class ApplicationConfig:
    """
    ApplicationConfig provides functions for dealing with the pysdk application
    Config.

    Config can be read from the following locations:

    - The default packaged file: application_config.conf
    - The default location: "~/.here/application_config.conf"
    - A custom path to a config file
    """

    def __init__(
        self,
        retry_max_time: float,
        read_timeout: float,
        connect_retry_max_time: float,
        connect_timeout: float,
        polling_wait: float,
        max_object_store_upload_part_size: int,
    ):
        """
        Instantiate the ApplicationConfig object.

        :param retry_max_time: Maximum retry time.
        :param read_timeout: Read timeout.
        :param connect_retry_max_time: Maximum retry time to connect.
        :param connect_timeout: Connection timeout.
        :param polling_wait: Polling wait time
        :param max_object_store_upload_part_size: Maximum object store upload part size in MB
        """
        self.retry_max_time: float = retry_max_time
        self.read_timeout: float = read_timeout
        self.connect_retry_max_time: float = connect_retry_max_time
        self.connect_timeout: float = connect_timeout
        self.polling_wait: float = polling_wait
        self.max_object_store_upload_part_size: int = max_object_store_upload_part_size

    @classmethod
    def from_default(cls) -> "ApplicationConfig":
        """Return the config read from the default config file path at
        '~/.here/application_config.conf'.

        If no default file is found, this method will try to read the
        config from the package application_config.conf file.

        :return: config
        """

        application_conf = cls.from_config_file(DEFAULT_APPLICATION_CONFIG_PATH)

        return application_conf

    @classmethod
    def from_config_file(cls, path: str) -> "ApplicationConfig":
        """
        Return the config object from a specified config file path.

        :param path: path to a PySDK application_config.conf file.
        :return: config
        :raises ConfigException: Erroneous application_config.conf file in path
        """

        try:
            config_path = expanduser(expandvars(path))
            config = PropertiesDict.parse(config_path)
            return ApplicationConfig(
                retry_max_time=_ensureValue(config, _constants.retry_max_time),
                connect_retry_max_time=_ensureValue(config, _constants.connect_retry_max_time),
                connect_timeout=_ensureValue(config, _constants.connect_timeout),
                read_timeout=_ensureValue(config, _constants.read_timeout),
                polling_wait=_ensureValue(config, _constants.polling_wait),
                max_object_store_upload_part_size=_ensureValue(
                    config, _constants.max_object_store_upload_part_size
                ),
            )
        except FileNotFoundError:
            this_dir = Path(__file__).parent
            default_path = this_dir / "application_config.conf"
            return cls.from_config_file(os.fspath(default_path))
        except Exception as e:
            raise ConfigException(f"Erroneous {config_path} file: {e}")
