#  Copyright (C) 2025
#  ABM, Moscow
#
#  UNPUBLISHED PROPRIETARY MATERIAL.
#  ALL RIGHTS RESERVED.
#
#  Authors: Mike Orlov <m.orlov@abm-jsc.ru>
from dataclasses import dataclass
from functools import cached_property
from typing import Mapping, ClassVar, Generic

from frozendict import frozendict
from http_tools import IncomingRequest
from init_helpers import Jsonable

from ...parameter import SpecParameter
from ...spec_ref import SpecRef
from ...spec_resource import SpecResource
from openapi_tools.spec.parameter import ParameterLocation, QueryParameter, PathParameter, HeaderParameter, CookieParameter
from .security_scheme import SecurityScheme, AuthInfo, AuthToken
from .security_scheme_type import SecuritySchemeType
from ..exceptions import Unauthorized


@dataclass(frozen=True)
class ApiKeySecurityScheme(SecurityScheme, Generic[AuthInfo]):
    type_: ClassVar[SecuritySchemeType] = SecuritySchemeType.api_key
    location: ParameterLocation
    name: str

    def get_spec_dict(self, dependency_to_ref: Mapping[SpecResource, SpecRef]) -> frozendict[str, Jsonable]:
        return SecurityScheme.get_spec_dict(self, dependency_to_ref) | {'in': self.location, 'name': self.name}

    def get_key(self) -> str:
        return f'{self.type_.name}_in_{self.location.name}_{self.name}'

    def __post_init__(self):
        assert self._parameter

    @cached_property
    def _parameter(self) -> SpecParameter:
        if self.location == ParameterLocation.query:
            return QueryParameter(name=self.name, schema=str)
        if self.location == ParameterLocation.path:
            return PathParameter(name=self.name, schema=str)
        if self.location == ParameterLocation.header:
            return HeaderParameter(name=self.name, schema=str)
        elif self.location == ParameterLocation.cookie:
            return CookieParameter(name=self.name, schema=str)
        raise NotImplementedError(f"ApiKeySecurityScheme does not support location {self.location!r} ")

    async def _extract_token(self, incoming_request: IncomingRequest) -> AuthToken:
        try:
            return await self._parameter.get(incoming_request, {})
        except KeyError:
            raise Unauthorized from None
