from typing import Any, Dict, Optional, Union

from pydantic import BaseModel, Field, Extra

from .example import Example
from .media_type import MediaType
from .reference import Reference
from .schema import Schema


class Parameter(BaseModel):
    """
    Describes a single operation parameter.

    A unique parameter is defined by a combination of a [name](#parameterName) and [location](#parameterIn).
    """

    """Fixed Fields"""

    name: str = ...
    """
    **REQUIRED**. The name of the parameter.
    Parameter names are *case sensitive*. 
    
    - If [`in`](#parameterIn) is `"path"`, the `name` field MUST correspond to a template expression occurring 
      within the [path](#pathsPath) field in the [Paths Object](#pathsObject).
      See [Path Templating](#pathTemplating) for further information.
    - If [`in`](#parameterIn) is `"header"` and the `name` field is `"Accept"`, `"Content-Type"` or `"Authorization"`, 
      the parameter definition SHALL be ignored.
    - For all other cases, the `name` corresponds to the parameter name used by the [`in`](#parameterIn) property.
    """

    param_in: str = Field(alias="in")
    """
    **REQUIRED**. The location of the parameter. Possible values are `"query"`, `"header"`, `"path"` or `"cookie"`.
    """

    description: Optional[str] = None
    """
    A brief description of the parameter.
    This could contain examples of use.
    [CommonMark syntax](https://spec.commonmark.org/) MAY be used for rich text representation.
    """

    required: bool = False
    """
    Determines whether this parameter is mandatory.
    If the [parameter location](#parameterIn) is `"path"`, this property is **REQUIRED** and its value MUST be `true`.
    Otherwise, the property MAY be included and its default value is `false`.
    """

    deprecated: bool = False
    """
    Specifies that a parameter is deprecated and SHOULD be transitioned out of usage.
    Default value is `false`.
    """

    allowEmptyValue: bool = False
    """
    Sets the ability to pass empty-valued parameters.
    This is valid only for `query` parameters and allows sending a parameter with an empty value.
    Default value is `false`.
    If [`style`](#parameterStyle) is used, and if behavior is `n/a` (cannot be serialized),
    the value of `allowEmptyValue` SHALL be ignored.
    Use of this property is NOT RECOMMENDED, as it is likely to be removed in a later revision.
    """

    """
    The rules for serialization of the parameter are specified in one of two ways.
    For simpler scenarios, a [`schema`](#parameterSchema) and [`style`](#parameterStyle)
    can describe the structure and syntax of the parameter.
    """

    style: Optional[str] = None
    """
    Describes how the parameter value will be serialized depending on the type of the parameter value.
    Default values (based on value of `in`):
    
    - for `query` - `form`;
    - for `path` - `simple`;
    - for `header` - `simple`;
    - for `cookie` - `form`.
    """

    explode: bool = False
    """
    When this is true, parameter values of type `array` or `object` generate separate parameters
    for each value of the array or key-value pair of the map.
    For other types of parameters this property has no effect.
    When [`style`](#parameterStyle) is `form`, the default value is `true`.
    For all other styles, the default value is `false`.
    """

    allowReserved: bool = False
    """
    Determines whether the parameter value SHOULD allow reserved characters,
    as defined by [RFC3986](https://tools.ietf.org/html/rfc3986#section-2.2)
    `:/?#[]@!$&'()*+,;=` to be included without percent-encoding.
    This property only applies to parameters with an `in` value of `query`.
    The default value is `false`.
    """

    param_schema: Optional[Union[Schema, Reference]] = Field(default=None, alias="schema")
    """
    The schema defining the type used for the parameter.
    """

    example: Optional[Any] = None
    """
    Example of the parameter's potential value.
    The example SHOULD match the specified schema and encoding properties if present.
    The `example` field is mutually exclusive of the `examples` field.
    Furthermore, if referencing a `schema` that contains an example, 
    the `example` value SHALL _override_ the example provided by the schema.
    To represent examples of media types that cannot naturally be represented in JSON or YAML,
    a string value can contain the example with escaping where necessary.
    """

    examples: Optional[Dict[str, Union[Example, Reference]]] = None
    """
    Examples of the parameter's potential value.
    Each example SHOULD contain a value in the correct format as specified in the parameter encoding.
    The `examples` field is mutually exclusive of the `example` field.
    Furthermore, if referencing a `schema` that contains an example,
    the `examples` value SHALL _override_ the example provided by the schema.
    """

    """
    For more complex scenarios, the [`content`](#parameterContent) property 
    can define the media type and schema of the parameter.
    A parameter MUST contain either a `schema` property, or a `content` property, but not both.
    When `example` or `examples` are provided in conjunction with the `schema` object,
    the example MUST follow the prescribed serialization strategy for the parameter.
    """

    content: Optional[Dict[str, MediaType]] = None
    """
    A map containing the representations for the parameter.
    The key is the media type and the value describes it.
    The map MUST only contain one entry.
    """

    class Config:
        extra = Extra.ignore
        allow_population_by_field_name = True
        schema_extra = {
            "examples": [
                {
                    "name": "token",
                    "in": "header",
                    "description": "token to be passed as a header",
                    "required": True,
                    "schema": {"type": "array", "items": {"type": "integer", "format": "int64"}},
                    "style": "simple",
                },
                {
                    "name": "username",
                    "in": "path",
                    "description": "username to fetch",
                    "required": True,
                    "schema": {"type": "string"},
                },
                {
                    "name": "id",
                    "in": "query",
                    "description": "ID of the object to fetch",
                    "required": False,
                    "schema": {"type": "array", "items": {"type": "string"}},
                    "style": "form",
                    "explode": True,
                },
                {
                    "in": "query",
                    "name": "freeForm",
                    "schema": {"type": "object", "additionalProperties": {"type": "integer"}},
                    "style": "form",
                },
                {
                    "in": "query",
                    "name": "coordinates",
                    "content": {
                        "application/json": {
                            "schema": {
                                "type": "object",
                                "required": ["lat", "long"],
                                "properties": {"lat": {"type": "number"}, "long": {"type": "number"}},
                            }
                        }
                    },
                },
            ]
        }
