# 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 pyhocon import ConfigFactory, ConfigMissingException

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 = ConfigFactory.parse_file(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 ConfigMissingException:
            raise ConfigException("Erroneous ", config_path, " file")
