from unittest import TestCase

from edpopenapibuilder.model import (
    ApiKeyInEnum,
    ApiKeySecuritySchemaModel,
    ComponentsModel,
    HttpSecuritySchemaModel,
    InfoModel,
    OpenapiModel,
    PathModel,
    PathParameterInEnum,
    PathParameterModel,
    SchemaPropertyModel,
    SchemaPropertyTypeEnum,
    SecuritySchemeTypeEnum,
)


class TestSecuritySchemeTypeEnum(TestCase):

    def test_api_key_value(self):
        self.assertEqual(SecuritySchemeTypeEnum.api_key, "apiKey")

    def test_http_value(self):
        self.assertEqual(SecuritySchemeTypeEnum.http, "http")

    def test_oauth2_value(self):
        self.assertEqual(SecuritySchemeTypeEnum.oauth2, "oauth2")

    def test_open_id_connect_value(self):
        self.assertEqual(SecuritySchemeTypeEnum.open_id_connect, "openIdConnect")


class TestApiKeyInEnum(TestCase):

    def test_header_value(self):
        self.assertEqual(ApiKeyInEnum.header, "header")

    def test_query_value(self):
        self.assertEqual(ApiKeyInEnum.query, "query")

    def test_cookie_value(self):
        self.assertEqual(ApiKeyInEnum.cookie, "cookie")


class TestApiKeySecuritySchemaModel(TestCase):

    def test_default_type_is_api_key(self):
        model = ApiKeySecuritySchemaModel(name="x-api-key")
        self.assertEqual(model.type, SecuritySchemeTypeEnum.api_key)

    def test_default_in_location_is_header(self):
        model = ApiKeySecuritySchemaModel(name="x-api-key")
        self.assertEqual(model.in_location, ApiKeyInEnum.header)

    def test_get_model_data_keys(self):
        model = ApiKeySecuritySchemaModel(
            in_location=ApiKeyInEnum.header,
            name="x-api-key",
        )
        data = model.get_model_data()
        self.assertIn("type", data)
        self.assertIn("in", data)
        self.assertIn("name", data)
        self.assertNotIn("inLocation", data)

    def test_get_model_data_values(self):
        model = ApiKeySecuritySchemaModel(
            in_location=ApiKeyInEnum.header,
            name="x-api-key",
        )
        data = model.get_model_data()
        self.assertEqual(str(data["type"]), "apiKey")
        self.assertEqual(str(data["in"]), "header")
        self.assertEqual(data["name"], "x-api-key")

    def test_in_location_query(self):
        model = ApiKeySecuritySchemaModel(
            in_location=ApiKeyInEnum.query,
            name="api_key",
        )
        data = model.get_model_data()
        self.assertEqual(str(data["in"]), "query")

    def test_in_location_cookie(self):
        model = ApiKeySecuritySchemaModel(
            in_location=ApiKeyInEnum.cookie,
            name="session",
        )
        data = model.get_model_data()
        self.assertEqual(str(data["in"]), "cookie")

    def test_name_none_excluded_from_model_data(self):
        model = ApiKeySecuritySchemaModel()
        data = model.get_model_data()
        self.assertNotIn("name", data)


class TestHttpSecuritySchemaModel(TestCase):

    def test_default_type_is_http(self):
        model = HttpSecuritySchemaModel(scheme="bearer")
        self.assertEqual(model.type, SecuritySchemeTypeEnum.http)

    def test_bearer_scheme(self):
        model = HttpSecuritySchemaModel(scheme="bearer", bearer_format="JWT")
        data = model.get_model_data()
        self.assertEqual(str(data["type"]), "http")
        self.assertEqual(data["scheme"], "bearer")
        self.assertEqual(data["bearerFormat"], "JWT")

    def test_basic_scheme(self):
        model = HttpSecuritySchemaModel(scheme="basic")
        data = model.get_model_data()
        self.assertEqual(data["scheme"], "basic")

    def test_bearer_format_none_excluded(self):
        model = HttpSecuritySchemaModel(scheme="basic")
        data = model.get_model_data()
        self.assertNotIn("bearerFormat", data)

    def test_bearer_format_camel_case_key(self):
        model = HttpSecuritySchemaModel(scheme="bearer", bearer_format="JWT")
        data = model.get_model_data()
        self.assertIn("bearerFormat", data)
        self.assertNotIn("bearer_format", data)


class TestComponentsModelSecuritySchemes(TestCase):

    def test_add_api_key_security_scheme(self):
        components = ComponentsModel()
        model = ApiKeySecuritySchemaModel(
            in_location=ApiKeyInEnum.header,
            name="x-api-key",
        )
        components.add_security_scheme("ApiKeyAuth", model)
        self.assertIn("ApiKeyAuth", components.security_schemes)
        self.assertIs(components.security_schemes["ApiKeyAuth"], model)

    def test_add_http_security_scheme(self):
        components = ComponentsModel()
        model = HttpSecuritySchemaModel(scheme="bearer", bearer_format="JWT")
        components.add_security_scheme("BearerAuth", model)
        self.assertIn("BearerAuth", components.security_schemes)

    def test_add_multiple_security_schemes(self):
        components = ComponentsModel()
        api_key = ApiKeySecuritySchemaModel(
            in_location=ApiKeyInEnum.header,
            name="x-api-key",
        )
        bearer = HttpSecuritySchemaModel(scheme="bearer", bearer_format="JWT")
        components.add_security_scheme("ApiKeyAuth", api_key)
        components.add_security_scheme("BearerAuth", bearer)
        self.assertEqual(len(components.security_schemes), 2)

    def test_security_schemes_in_to_dict(self):
        components = ComponentsModel()
        model = ApiKeySecuritySchemaModel(
            in_location=ApiKeyInEnum.header,
            name="x-api-key",
        )
        components.add_security_scheme("ApiKeyAuth", model)
        data = components.to_dict()
        self.assertIn("securitySchemes", data)
        self.assertIn("ApiKeyAuth", data["securitySchemes"])

    def test_no_security_schemes_excluded_from_model_data(self):
        components = ComponentsModel()
        data = components.get_model_data()
        self.assertNotIn("securitySchemes", data)


class TestSecurityParameterSuppression(TestCase):

    def _build_model_with_api_key(self) -> OpenapiModel:
        model = OpenapiModel(info=InfoModel(title="Test", version="1.0.0"))
        model.components = ComponentsModel()
        model.components.add_security_scheme(
            "ApiKeyAuth",
            ApiKeySecuritySchemaModel(in_location=ApiKeyInEnum.header, name="x-api-key"),
        )
        return model

    def _make_path_with_params(self, *param_names_locations) -> PathModel:
        path = PathModel()
        for name, location in param_names_locations:
            path.add_parameter(PathParameterModel(
                path_in=location,
                name=name,
                schema=SchemaPropertyModel(type=SchemaPropertyTypeEnum.string),
                required=True,
            ))
        return path

    def test_suppresses_api_key_header_parameter(self):
        model = self._build_model_with_api_key()
        path = self._make_path_with_params(
            ("x-api-key", PathParameterInEnum.header),
        )
        path.add_security("ApiKeyAuth")
        model.add_path("/test", "GET", path)

        data = model.to_dict()
        path_params = data["paths"]["/test"]["get"].parameters
        self.assertEqual(len(path_params), 0)

    def test_keeps_non_security_parameters(self):
        model = self._build_model_with_api_key()
        path = self._make_path_with_params(
            ("x-api-key", PathParameterInEnum.header),
            ("x-merchant-id", PathParameterInEnum.header),
        )
        path.add_security("ApiKeyAuth")
        model.add_path("/test", "POST", path)

        data = model.to_dict()
        path_params = data["paths"]["/test"]["post"].parameters
        self.assertEqual(len(path_params), 1)
        self.assertEqual(path_params[0].name, "x-merchant-id")

    def test_no_suppression_without_security_on_path(self):
        model = self._build_model_with_api_key()
        path = self._make_path_with_params(
            ("x-api-key", PathParameterInEnum.header),
        )
        model.add_path("/test", "GET", path)

        data = model.to_dict()
        path_params = data["paths"]["/test"]["get"].parameters
        self.assertEqual(len(path_params), 1)

    def test_no_suppression_without_components(self):
        model = OpenapiModel(info=InfoModel(title="Test", version="1.0.0"))
        path = self._make_path_with_params(
            ("x-api-key", PathParameterInEnum.header),
        )
        path.add_security("ApiKeyAuth")
        model.add_path("/test", "GET", path)

        data = model.to_dict()
        path_params = data["paths"]["/test"]["get"].parameters
        self.assertEqual(len(path_params), 1)

    def test_does_not_suppress_same_name_different_location(self):
        model = self._build_model_with_api_key()
        path = self._make_path_with_params(
            ("x-api-key", PathParameterInEnum.query),
        )
        path.add_security("ApiKeyAuth")
        model.add_path("/test", "GET", path)

        data = model.to_dict()
        path_params = data["paths"]["/test"]["get"].parameters
        self.assertEqual(len(path_params), 1)