from dataclasses import dataclass, field, fields
from typing import List, Dict, Optional, Union

from ._BaseModel import BaseModel
from ._ExternalDocsModel import ExternalDocsModel
from ._PathParameterInEnum import PathParameterInEnum
from ._PathParameterModel import PathParameterModel
from ._PathResponseModel import PathResponseModel
from ._RequestBodyModel import RequestBodyModel
from ..utils import set_dict_path_value


@dataclass
class PathModel(BaseModel):
    summary: str = None
    description: str = None
    operation_id: str = None
    parameters: List[PathParameterModel] = field(default_factory=list)
    request_body: RequestBodyModel = None
    responses: Dict[str, PathResponseModel] = field(default_factory=dict)
    tags: List[str] = field(default_factory=list)
    deprecated: bool = None
    security: List[Dict[str, List[str]]] = None
    external_docs: Optional[ExternalDocsModel] = None

    _parameters_dict: Dict[str, Dict] = field(default_factory=dict)

    # --- parameter ---

    def add_parameter(self, parameter: PathParameterModel) -> None:
        if not self.has_parameter(parameter.name):
            self._parameters_dict[parameter.name] = {
                'parameter': parameter,
                'index': len(self.parameters),
            }
            self.parameters.append(parameter)
        else:
            param_def = self._parameters_dict[parameter.name]
            param_def['parameter'] = parameter
            self.parameters[param_def['index']] = parameter

    def get_parameter(self, name: str) -> Optional[PathParameterModel]:
        if not self.has_parameter(name):
            return None
        return self._parameters_dict[name]['parameter']

    def has_parameter(self, name: str) -> bool:
        return name in self._parameters_dict

    # --- security ---

    def add_security(self, name: str, scopes: List[str] = None) -> None:
        if self.security is None:
            self.security = []
        if self.has_security(name):
            self.get_security(name)[name] = scopes or []
        else:
            self.security.append({name: scopes or []})

    def has_security(self, name: str) -> bool:
        if self.security is None:
            return False
        return any(name in entry for entry in self.security)

    def get_security(self, name: str) -> Optional[Dict[str, List[str]]]:
        if self.security is None:
            return None
        for entry in self.security:
            if name in entry:
                return entry
        return None

    # --- response ---

    def add_response(self, code: Union[int, str], response: PathResponseModel) -> None:
        if self.responses is None:
            self.responses = {}

        set_dict_path_value(
            dict_value=self.responses,
            path=[str(code)],
            value=response
        )

    def get_response(self, code: Union[int, str]) -> Optional[PathResponseModel]:
        if self.responses is None:
            return None
        return self.responses.get(str(code))

    def has_response(self, code: Union[int, str]) -> bool:
        if self.responses is None:
            return False
        return str(code) in self.responses

    # --- fluent builder ---

    def set(self, **kwargs) -> 'PathModel':
        valid = {f.name for f in fields(self)}
        for key, value in kwargs.items():
            if key not in valid:
                raise AttributeError(f"Unknown field: {key}")
            setattr(self, key, value)
        return self

    def parameter(self, name: str) -> PathParameterModel:
        existing = self.get_parameter(name)
        if existing is not None:
            return existing
        param = PathParameterModel(name=name, path_in=PathParameterInEnum.query)
        self.add_parameter(param)
        return param

    def response(self, code: Union[int, str]) -> PathResponseModel:
        existing = self.get_response(code)
        if existing is not None:
            return existing
        new_response = PathResponseModel(description="")
        self.add_response(code, new_response)
        return new_response

    def with_security(self, name: str, scopes: List[str] = None) -> 'PathModel':
        self.add_security(name, scopes)
        return self
