from dataclasses import dataclass
from typing import Dict, List, Optional, Set, Tuple

from ._ApiKeySecuritySchemaModel import ApiKeySecuritySchemaModel
from ._BaseModel import BaseModel
from ._ComponentsModel import ComponentsModel
from ._ExternalDocsModel import ExternalDocsModel
from ._InfoModel import InfoModel
from ._PathModel import PathModel
from ._ServerModel import ServerModel
from ._TagModel import TagModel
from ..utils import set_dict_path_value


@dataclass
class OpenapiModel(BaseModel):
    info: InfoModel
    servers: List[ServerModel] = None
    components: ComponentsModel = None
    paths: Dict[str, Dict[str, PathModel]] = None
    tags: List[TagModel] = None
    external_docs: Optional[ExternalDocsModel] = None
    security: List[Dict[str, List[str]]] = None

    def add_path(self, url: str, method: str, path: PathModel) -> PathModel:
        if self.paths is None:
            self.paths = {}

        set_dict_path_value(
            dict_value=self.paths,
            path=[url, method.lower()],
            value=path
        )

        return path

    def add_server(self, server: ServerModel) -> ServerModel:
        if self.servers is None:
            self.servers = []
        self.servers.append(server)

        return server

    def _get_security_covered_params(self) -> Dict[str, Set[Tuple[str, str]]]:
        """Map scheme name -> set of (param_name, param_location) covered by that scheme."""
        if self.components is None or self.components.security_schemes is None:
            return {}

        covered = {}
        for scheme_name, scheme in self.components.security_schemes.items():
            if isinstance(scheme, ApiKeySecuritySchemaModel) and scheme.name:
                covered[scheme_name] = {(scheme.name, str(scheme.in_location))}
        return covered

    def _filter_security_covered_parameters(self):
        if self.paths is None:
            return

        covered_by_scheme = self._get_security_covered_params()
        if not covered_by_scheme:
            return

        for url, methods in self.paths.items():
            for method, path_model in methods.items():
                if path_model.security is None:
                    continue

                covered = set()
                for security_entry in path_model.security:
                    for scheme_name in security_entry:
                        if scheme_name in covered_by_scheme:
                            covered.update(covered_by_scheme[scheme_name])

                if covered:
                    path_model.parameters = [
                        p for p in path_model.parameters
                        if (p.name, str(p.path_in)) not in covered
                    ]

    def to_dict(self) -> dict:
        self._filter_security_covered_parameters()
        return {
            "openapi": "3.0.0",
            **super().to_dict(),
        }
