import logging
import time
from pathlib import Path
from typing import Optional
from typing import Tuple
from typing import Union

from pydantic import BaseSettings
from pydantic import SecretStr
from pydantic import validator

from click.types import LazyFile

from bulk_restore_tool.models import Device


class UTCFormatter(logging.Formatter):
    converter = time.gmtime


brt_format = UTCFormatter(
    fmt="%(asctime)s %(levelname)s %(name)s - %(message)s", datefmt="%Y-%m-%dT%H:%M:%SZ"
)


class BRTSettings(BaseSettings):
    """
    Setting configuration class for the Bulk Restore Tool.

    Inheriting from [pydantic.BaseSettings](https://pydantic-docs.helpmanual.io/usage/settings/) enables environment
    variable and .env file fallbacks if values are not passed in during instantiation, allowing flexible configuration
    from multiple possible sources, whichever is easier for the end-user.
    """

    api_client_id: str
    api_client_secret: SecretStr
    url: str
    log_file: str = "bulk_restore.log"
    log_level: Union[int, str] = "INFO"
    logger: Optional[logging.Logger] = None
    disable_device_logs: bool = False
    device_log_level: Union[int, str] = "INFO"
    _device_loggers: Optional[dict] = None
    downloads_per_archive: int = 1

    def __init__(self, **kwargs):
        # clear any keys from kwargs that are passed as None, which allows fallback to values in ENV
        kwargs = {k: v for k, v in kwargs.items() if v is not None}
        super().__init__(**kwargs)

    @validator("log_file")
    def _validate_log_file(cls, value):  # noqa
        if "~" in value:
            value = str(value.replace("~", str(Path.home())))

        # will raise if not valid for writing but won't create file
        LazyFile(value, "a")

        return value

    @validator("logger")
    def _create_logger(cls, value, values):
        if value is None:
            logger = logging.getLogger("main")
            logger.setLevel(values["log_level"])
            if not logger.handlers:
                handler = logging.FileHandler(filename=values["log_file"], delay=True)
                handler.setFormatter(brt_format)
                logger.addHandler(handler)
            return logger
        return value

    def get_device_loggers(
        self, device: Device, target: Path
    ) -> Tuple[logging.Logger, logging.Logger]:
        if self._device_loggers is None:
            self._device_loggers = {}

        if device.deviceId not in self._device_loggers:
            device_logger = logging.getLogger(device.deviceId)
            device_filename = f"{device.username}_{device.name}_{device.deviceId}.log"
            device_handler = logging.FileHandler(
                filename=target / device_filename, delay=True
            )
            device_handler.setFormatter(brt_format)
            device_logger.addHandler(device_handler)
            device_logger.handlers.extend(self.logger.handlers)
            device_logger.setLevel(self.device_log_level)

            error_logger = logging.getLogger(f"{device.deviceId}-error")
            error_filename = (
                f"{device.username}_{device.name}_{device.deviceId}_error.log"
            )
            error_handler = logging.FileHandler(
                filename=target / error_filename, delay=True
            )
            error_handler.setFormatter(brt_format)
            error_logger.addHandler(error_handler)
            error_logger.handlers.extend(self.logger.handlers)
            error_logger.setLevel(self.device_log_level)

            self._device_loggers[device.deviceId] = (device_logger, error_logger)

        return self._device_loggers[device.deviceId]

    class Config:
        underscore_attrs_are_private = True
        env_prefix = "brt_"
        env_file = ".env"
