# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.

from typing import Any

from keystone.api.validation import parameter_types
from keystone.api.validation import response_types
from keystone.common import validation

basic_property_id = {
    'type': 'object',
    'properties': {'id': {'type': 'string'}},
    'required': ['id'],
    'additionalProperties': False,
}

remote_id = {"type": "string", "maxLength": 64}

saml_create = {
    'type': 'object',
    'properties': {
        'identity': {
            'type': 'object',
            'properties': {
                'token': basic_property_id,
                'methods': {'type': 'array'},
            },
            'required': ['token'],
            'additionalProperties': False,
        },
        'scope': {
            'type': 'object',
            'properties': {'service_provider': basic_property_id},
            'required': ['service_provider'],
            'additionalProperties': False,
        },
    },
    'required': ['identity', 'scope'],
    'additionalProperties': False,
}

_service_provider_properties = {
    # NOTE(rodrigods): The database accepts URLs with 256 as max length,
    # but parameter_types.url uses 225 as max length.
    "auth_url": {
        **parameter_types.url,
        "description": "The URL to authenticate against",
    },
    "sp_url": {
        **parameter_types.url,
        "description": "The service provider's URL",
    },
    "description": {
        "type": ["string", "null"],
        "description": "The description of the service provider",
    },
    "enabled": {
        **parameter_types.boolean,
        "description": "Whether the service provider is enabled or not",
    },
    "relay_state_prefix": {
        "type": ["string", "null"],
        "description": "The prefix of the RelayState SAML attribute",
    },
}

service_provider_schema: dict[str, Any] = {
    "type": "object",
    "description": "A service provider object",
    "properties": {
        "id": {
            "type": "string",
            "readOnly": True,
            "description": "The service provider ID",
        },
        "links": response_types.resource_links,
        **_service_provider_properties,
    },
    "additionalProperties": True,
}

service_provider_index_request_query: dict[str, Any] = {
    "type": "object",
    "properties": {
        "id": {"type": "string", "description": "The service provider ID"},
        "enabled": {
            **parameter_types.boolean,
            "description": "Whether the service provider is enabled or not",
        },
    },
    # TODO(stephenfin): Change this to False once we have schemas for all
    # resources. Doing so will remove comparator (id__icontains) support.
    "additionalProperties": True,
}

service_provider_index_response_body: dict[str, Any] = {
    "type": "object",
    "properties": {
        "service_providers": {
            "type": "array",
            "items": service_provider_schema,
            "description": "A list of service provider objects",
        },
        "links": response_types.links,
        "truncated": response_types.truncated,
    },
    "additionalProperties": False,
}

service_provider_response_body: dict[str, Any] = {
    "type": "object",
    "description": "A service provider object",
    "properties": {"service_provider": service_provider_schema},
    "additionalProperties": False,
}

service_provider_create_request_body: dict[str, Any] = {
    "type": "object",
    "description": "A service provider object",
    "properties": {
        "service_provider": {
            "type": "object",
            "properties": _service_provider_properties,
            "required": ["auth_url", "sp_url"],
            "additionalProperties": False,
        }
    },
    "required": ["service_provider"],
    "additionalProperties": False,
}

service_provider_update_request_body: dict[str, Any] = {
    "type": "object",
    "description": "A service provider object",
    "properties": {
        # NOTE(rodrigods): 'id' is not required since it is passed in the URL
        "service_provider": {
            "type": "object",
            "properties": _service_provider_properties,
            "additionalProperties": False,
            "minProperties": 1,
        }
    },
    "required": ["service_provider"],
    "additionalProperties": False,
}

identity_provider_response_links: dict[str, Any] = {
    "type": "object",
    "properties": {
        "self": {"type": "string", "format": "uri"},
        "protocols": {"type": "string", "format": "uri"},
    },
    "additionalProperties": False,
    "readOnly": True,
}

_identity_provider_properties = {
    "enabled": {
        **parameter_types.boolean,
        "description": "Whether the identity provider is enabled or not",
    },
    "description": {
        "type": ["string", "null"],
        "description": "The identity provider description",
    },
    "authorization_ttl": {
        "type": ["integer", "null"],
        "minimum": 0,
        "description": (
            "The length of validity in minutes for group memberships "
            "carried over through mapping and persisted in the database. "
            "If left unset, the default value configured in keystone will "
            "be used, if enabled."
        ),
    },
    "remote_ids": {
        "type": ["array", "null"],
        "description": "List of the unique identity provider's remote IDs",
        "items": remote_id,
        "uniqueItems": True,
    },
}

identity_provider_schema: dict[str, Any] = {
    "type": "object",
    "description": "An identity provider object",
    "properties": {
        "id": {
            "type": "string",
            "readOnly": True,
            "description": "The identity provider ID",
        },
        "links": identity_provider_response_links,
        **_identity_provider_properties,
    },
    "additionalProperties": True,
}

identity_provider_index_request_query: dict[str, Any] = {
    "type": "object",
    "properties": {
        "id": {"type": "string", "description": "The identity provider ID"},
        "enabled": {
            **parameter_types.boolean,
            "description": "Whether the identity provider is enabled or not",
        },
    },
    # TODO(stephenfin): Change this to False once we have schemas for all
    # resources. Doing so will remove comparator (name__icontains) support.
    "additionalProperties": True,
}

identity_provider_index_response_body: dict[str, Any] = {
    "type": "object",
    "properties": {
        "identity_providers": {
            "type": "array",
            "items": identity_provider_schema,
            "description": "A list of identity provider objects",
        },
        "links": response_types.links,
    },
    "additionalProperties": True,
}

identity_provider_request_query: dict[str, Any] = {
    "type": "object",
    "properties": {},
    # TODO(stephenfin): Change this to False once we have schemas for all
    # resources.
    "additionalProperties": True,
}

identity_provider_response_body: dict[str, Any] = {
    "type": "object",
    "description": "An identity provider object",
    "properties": {"identity_provider": identity_provider_schema},
    "additionalProperties": False,
}

identity_provider_create_request_body: dict[str, Any] = {
    "type": "object",
    "description": "An identity provider object",
    "properties": {
        "identity_provider": {
            "type": "object",
            "properties": {
                **_identity_provider_properties,
                "domain_id": {
                    **validation.nullable(parameter_types.domain_id),
                    "description": (
                        "The ID of a domain that is associated with the "
                        "identity provider. Federated users that "
                        "authenticate with the identity provider will be "
                        "created under the domain specified."
                    ),
                },
            },
            "additionalProperties": False,
        }
    },
    "additionalProperties": False,
}

identity_provider_update_request_body: dict[str, Any] = {
    "type": "object",
    "description": "An identity provider object",
    "properties": {
        "identity_provider": {
            "type": "object",
            "properties": _identity_provider_properties,
            "additionalProperties": False,
            "minProperties": 1,
        }
    },
    "required": ["identity_provider"],
}

mapping_id: dict[str, Any] = {
    "type": "string",
    "minLength": 1,
    "maxLength": 64,
}

protocol_id = {"type": "string", "minLength": 1, "maxLength": 64}

protocol_response_links: dict[str, Any] = {
    "type": "object",
    "properties": {
        "self": {"type": "string", "format": "uri"},
        "identity_provider": {"type": "string", "format": "uri"},
    },
    "additionalProperties": False,
    "readOnly": True,
}

# NOTE(0weng): remote_id_attribute is only and always returned in
# the list operation.
_protocol_properties = {
    "mapping_id": mapping_id,
    "remote_id_attribute": validation.nullable(remote_id),
}

# Common schema of `Federation Protocol` resource
protocol_schema: dict[str, Any] = {
    "type": "object",
    "description": "A federation protocol object.",
    "properties": {
        "id": protocol_id,
        "links": protocol_response_links,
        "idp_id": identity_provider_schema["properties"]["id"],
        **_protocol_properties,
    },
    "required": ["id", "idp_id", "mapping_id", "remote_id_attribute"],
    "additionalProperties": False,
}

# Short schema of `Federation Protocol` resource
protocol_schema_short: dict[str, Any] = {
    "type": "object",
    "description": "A federation protocol object.",
    "properties": {
        "id": parameter_types.name,
        "links": protocol_response_links,
        "mapping_id": mapping_id,
        "remote_id_attribute": remote_id,
    },
    "required": ["id", "mapping_id"],
    "additionalProperties": False,
}

# Response body of the
# `GET /OS-FEDERATION/identity_providers/{idp_id}/protocols` API operation
# returning a list of federation protocols
protocols_index_response_body: dict[str, Any] = {
    "type": "object",
    "properties": {
        "links": response_types.links,
        "protocols": {"type": "array", "items": protocol_schema},
    },
    "required": ["protocols"],
    "additionalProperties": False,
}

# Response body of API operations returning a single federation protocol
# `GET /OS-FEDERATION/identity_providers/{idp_id}/protocols/{protocol_id}`,
# `PUT /OS-FEDERATION/identity_providers/{idp_id}/protocols/{protocol_id}`, and
# `PATCH /OS-FEDERATION/identity_providers/{idp_id}/protocols/{protocol_id}`
protocol_show_response_body: dict[str, Any] = {
    "type": "object",
    "properties": {"protocol": protocol_schema_short},
    "required": ["protocol"],
    "additionalProperties": False,
}

# Request body of the
# `PUT /OS-FEDERATION/identity_providers/{idp_id}/protocols/{protocol_id}`
# API operation
protocol_create_request_body = {
    "type": "object",
    "properties": {
        "protocol": {
            "type": "object",
            "properties": _protocol_properties,
            "additionalProperties": False,
            "required": ["mapping_id"],
        }
    },
    "required": ["protocol"],
    "additionalProperties": False,
}

# Request body of the
# `PATCH /OS-FEDERATION/identity_providers/{idp_id}/protocols/{protocol_id}`
# API operation
protocol_update_request_body = {
    "type": "object",
    "properties": {
        "protocol": {
            "type": "object",
            # As of now, all protocol properties can be updated;
            # however, remote_id_attribute cannot be set to null
            # (but the empty string is fine) or removed.
            "properties": {
                "mapping_id": mapping_id,
                "remote_id_attribute": remote_id,
            },
            "additionalProperties": False,
            "minProperties": 1,
            "required": ["mapping_id"],
        }
    },
    "required": ["protocol"],
    "additionalProperties": False,
}
