import os
import platform
from functools import cached_property
from typing import Dict, List, Optional, Type, cast

from denial import InnerNoneType
from printo import repred
from simtypes import from_string

from skelet.errors import CaseError
from skelet.sources.abstract import AbstractSource, ExpectedType

sentinel = InnerNoneType()

@repred(filters={'prefix': lambda x: x != '', 'postfix': lambda x: x != '', 'case_sensitive': lambda x: x != False})  # type: ignore[arg-type]
class EnvSource(AbstractSource[ExpectedType]):
    def __init__(self, prefix: Optional[str] = '', postfix: Optional[str] = '', case_sensitive: bool = False) -> None:
        if platform.system() == 'Windows' and case_sensitive:
            raise OSError('On Windows, the environment variables are case-independent.')  # pragma: no cover

        self.prefix = prefix
        self.postfix = postfix
        self.case_sensitive = case_sensitive

    def __getitem__(self, key: str) -> str:  # type: ignore[override]
        full_key = f'{self.prefix}{key}{self.postfix}'
        if not self.case_sensitive:  # pragma: no cover
            full_key = full_key.upper()

        return self.data[full_key]

    @cached_property
    def data(self) -> Dict[str, str]:
        if self.case_sensitive:  # pragma: no cover
            return os.environ.copy()

        result = {}
        seen_keys: Dict[str, str] = {}
        for key, value in os.environ.items():
            capitalized_key = key.upper()
            if capitalized_key in seen_keys:
                if os.environ[key] != os.environ[seen_keys[capitalized_key]]:  # pragma: no cover
                    raise CaseError(f'There are 2 environment variables that are written the same way when capitalized: "{key}" and "{seen_keys[capitalized_key]}".')
            seen_keys[capitalized_key] = key
            result[capitalized_key] = value

        return result

    def type_awared_get(self, key: str, hint: Type[ExpectedType], default: ExpectedType = cast(ExpectedType, sentinel)) -> Optional[ExpectedType]:  # noqa: B008
        subresult = cast(str, self.get(key, default))

        if subresult is default:
            if default is not sentinel:
                return default
            return None

        return from_string(subresult, hint)

    @classmethod
    def for_library(cls, library_name: str) -> List['EnvSource[ExpectedType]']:
        if not library_name.isidentifier():
            raise ValueError('The library name can only be a valid Python identifier.')

        return [cls(prefix=f'{library_name}_'.upper())]
