import os
import logging
from pathlib import Path
from dataclasses import field, dataclass
from typing import TypedDict, Literal, Optional

from istari_digital_client.env import env_bool, env_int, env_str, env_cache_root

BearerAuthSetting = TypedDict(
    "BearerAuthSetting",
    {
        "type": Literal["bearer"],
        "in": Literal["header"],
        "key": Literal["Authorization"],
        "value": str,
    },
)

AuthSettings = TypedDict(
    "AuthSettings",
    {
        "RequestAuthenticator": BearerAuthSetting,
    },
    total=False,
)


@dataclass
class Configuration:
    """
    Client configuration for the Istari Digital SDK.

    This class provides runtime configuration options for the SDK, including registry
    connection settings, retry policies, filesystem cache behavior, logging options,
    and multipart upload settings. Values are loaded from environment variables and
    can be overridden at runtime.

    Most configuration values are optional. Defaults are applied via helper functions
    that read from environment variables with appropriate fallbacks.
    """

    registry_url: Optional[str] = field(
        default_factory=env_str("ISTARI_REGISTRY_URL", default=None)
    )
    registry_auth_token: Optional[str] = field(
        default_factory=env_str("ISTARI_REGISTRY_AUTH_TOKEN")
    )
    http_request_timeout_secs: Optional[int] = field(
        default_factory=env_int("ISTARI_CLIENT_HTTP_REQUEST_TIMEOUT_SECS"),
    )
    # === Retry config fields ===
    retry_enabled: Optional[bool] = field(
        default_factory=env_bool("ISTARI_CLIENT_RETRY_ENABLED", default=True)
    )
    retry_max_attempts: Optional[int] = field(
        default_factory=env_int("ISTARI_CLIENT_RETRY_MAX_ATTEMPTS")
    )
    retry_min_interval_millis: Optional[int] = field(
        default_factory=env_int("ISTARI_CLIENT_RETRY_MIN_INTERVAL_MILLIS")
    )
    retry_max_interval_millis: Optional[int] = field(
        default_factory=env_int("ISTARI_CLIENT_RETRY_MAX_INTERVAL_MILLIS")
    )
    # === Filesystem cache config fields ===
    filesystem_cache_enabled: Optional[bool] = field(
        default_factory=env_bool("ISTARI_CLIENT_FILESYSTEM_CACHE_ENABLED", default=True)
    )
    filesystem_cache_root: Path = field(
        default_factory=env_cache_root("ISTARI_CLIENT_FILESYSTEM_CACHE_ROOT")
    )
    filesystem_cache_clean_on_exit: Optional[bool] = field(
        default_factory=env_bool(
            "ISTARI_CLIENT_FILESYSTEM_CACHE_CLEAN_BEFORE_EXIT", default=True
        )
    )
    retry_jitter_enabled: Optional[bool] = field(
        default_factory=env_bool("ISTARI_CLIENT_RETRY_JITTER_ENABLED", default=True)
    )
    # === Multipart upload config fields ===
    multipart_chunksize: Optional[int] = field(
        default_factory=lambda: (
            val if (val := env_int("ISTARI_CLIENT_MULTIPART_CHUNKSIZE")()) is not None
            else 128 * 1024 * 1024
        )
    )

    multipart_threshold: Optional[int] = field(
        default_factory=lambda: (
            val if (val := env_int("ISTARI_CLIENT_MULTIPART_THRESHOLD")()) is not None
            else 2 * 1024 * 1024 * 1024
        )
    )

    # === Logging config fields ===
    log_level: Optional[str] = field(
        default_factory=env_str("ISTARI_CLIENT_LOG_LEVEL", default="INFO")
    )
    log_to_file: Optional[bool] = field(
        default_factory=env_bool("ISTARI_CLIENT_LOG_TO_FILE", default=False)
    )
    log_file_path: Optional[str] = field(
        default_factory=env_str("ISTARI_CLIENT_LOG_FILE_PATH", default=None)
    )

    # === Date and time formats ===
    datetime_format: str = field(init=False, default="%Y-%m-%dT%H:%M:%S.%f%z")
    date_format: str = field(init=False, default="%Y-%m-%d")

    def __post_init__(self) -> None:
        os.environ["ISTARI_REGISTRY_URL"] = self.registry_url or ""
        os.environ["ISTARI_REGISTRY_AUTH_TOKEN"] = self.registry_auth_token or ""

        self._configure_logging()

    def _configure_logging(self) -> None:
        """Configures logging based on the configuration settings."""
        log_level_str = (self.log_level or "INFO").upper()
        log_level = getattr(logging, log_level_str, logging.INFO)

        if not isinstance(log_level, int):
            raise ValueError(f"Invalid log level: {self.log_level}")

        logger = logging.getLogger("istari_digital_client")
        logger.setLevel(log_level)

        formatter = logging.Formatter(
            "%(asctime)s -  %(name)s:%(funcName)s:%(lineno)d - %(levelname)s - %(message)s",
            datefmt="%Y-%m-%d %H:%M:%S",
        )

        handlers: list[logging.Handler] = []

        if self.log_to_file:
            if not self.log_file_path:
                raise ConfigurationError(
                    "ISTARI_CLIENT_LOG_FILE_PATH must be set when ISTARI_CLIENT_LOG_TO_FILE=true"
                )

            log_dir = Path(self.log_file_path).parent
            if not log_dir.exists():
                raise ConfigurationError(
                    f"Directory does not exist for log file: {log_dir}"
                )

            file_handler = logging.FileHandler(self.log_file_path)
            file_handler.setFormatter(formatter)
            handlers.append(file_handler)

        stream_handler = logging.StreamHandler()
        stream_handler.setFormatter(formatter)
        handlers.append(stream_handler)

        for handler in handlers:
            logger.addHandler(handler)

        logger.propagate = False

    def auth_settings(self) -> AuthSettings:
        """Gets Auth Settings dict for api client.

        :return: The Auth Settings information dict.
        """
        auth: AuthSettings = {}
        if self.registry_auth_token is not None:
            auth["RequestAuthenticator"] = {
                "type": "bearer",
                "in": "header",
                "key": "Authorization",
                "value": "Bearer " + self.registry_auth_token,
            }
        return auth


class ConfigurationError(ValueError):
    pass
