"""
Copyright 2021 Daniel Afriyie

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    https://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
"""

import abc
import json
import threading
from typing import Any, Optional, override

from ru.utils import get_data
from ru.constants import constants
from ru.hints import Cast, Path, CastClass
from ru.exceptions.exceptions import ConfigKeyError, ConfigFileNotFoundError


class BaseConfig(abc.ABC):
    """ "
    Base config class for all config classes
    """

    def __init__(self, path: Path) -> None:
        self._path = path
        self._config = self.load()
        self._mutex = threading.RLock()

    @property
    def config(self) -> dict[str, str]:
        return self._config

    @abc.abstractmethod
    def save(
            self,
            filename: Path = "config.txt",
            encoding: str = constants.ENCODING,
    ) -> None:
        pass

    @abc.abstractmethod
    def load_config(self) -> dict[str, str]:
        pass

    @staticmethod
    def _cast[T](item: str, cast: Cast[T]) -> T | CastClass:
        if cast is not None:
            return cast(item.strip())
        return item

    def get[T](self, item: str, default: Any = None, cast: Optional[Cast[T]] = None) -> str | CastClass | T:
        with self._mutex:
            try:
                val = self._config[item]
                if cast:
                    return self._cast(val, cast)
                return val
            except KeyError:
                return default

    def get_as_tupple[T](self, item: str, cast: Optional[Cast[T]] = None) -> tuple[str | T | CastClass, ...]:
        with self._mutex:
            items: list[str] = self._config[item].split(",")
            if cast is None:
                return tuple(items)
            return tuple(self._cast(val, cast) for val in items)

    def load(self) -> dict[str, str]:
        try:
            return self.load_config()
        except FileNotFoundError:
            raise ConfigFileNotFoundError(
                f"{self.__class__.__name__}: Config file '{self._path}' not found!"
            )

    def __getitem__(self, item: str) -> str:
        with self._mutex:
            try:
                return self._config[item]
            except KeyError:
                raise ConfigKeyError(f"{item}")

    def __setitem__(self, key: str, value: str) -> None:
        with self._mutex:
            self._config[key] = value

    @override
    def __repr__(self) -> str:
        return str(self._config)


class JsonConfig(BaseConfig):

    def __init__(self, path: Path = "config.json") -> None:
        super().__init__(path)

    @override
    def load_config(self) -> dict[str, str]:
        with open(self._path, encoding=constants.ENCODING) as f:
            return json.load(f)

    @override
    def save(self, filename: Path = "config.txt", encoding: str = constants.ENCODING) -> None:
        with open(filename, "w", encoding=encoding) as f:
            json.dump(self._config, f, indent=4)


class TextConfig(BaseConfig):

    def __init__(self, path: Path = "config.txt"):
        super().__init__(path)

    @override
    def load_config(self) -> dict[str, str]:
        config = dict[str, str]()
        data = get_data(self._path, split=True, split_char="\n", filter_blanks=True)
        for d in data:
            try:
                split = d.split("=")
                key = split.pop(0)
                val = "=".join(split)
                config[key] = val
            except ValueError:
                pass

        return config

    @override
    def save(self, filename: Path = "config.txt", encoding: str = constants.ENCODING) -> None:
        with open(filename, "w", encoding=encoding) as f:
            text = "\n".join(
                [f"{key}={self._config[key]}" for key in self._config.keys()]
            )
            f.write(text)
