"""Base event model definitions."""

import sys
from datetime import datetime
from ipaddress import IPv4Address
from pathlib import Path
from typing import Dict, Optional, Union

from pydantic import AnyHttpUrl, BaseModel, constr

if sys.version_info >= (3, 8):
    from typing import Literal
else:
    from typing_extensions import Literal


class BaseModelWithConfig(BaseModel):
    """Pydantic model for base configuration shared among all models."""

    class Config:  # noqa: D106
        extra = "forbid"


class ContextModuleField(BaseModelWithConfig):
    """Pydantic model for `context`.`module` field.

    Attributes:
        usage_key (str): Consists of a block ID of the current component.
        display_name (str): Consists of a short description or title of the component.
    """

    usage_key: constr(regex=r"^block-v1:.+\+.+\+.+type@.+@[a-f0-9]{32}$")
    display_name: str
    original_usage_key: Optional[
        constr(regex=r"^block-v1:.+\+.+\+.+type@problem\+block@[a-f0-9]{32}$")
    ]
    original_usage_version: Optional[str]


class BaseContextField(BaseModelWithConfig):
    """Pydantic model for core `context` field.

    Attributes:
        course_user_tags (Dict of str): Content from `user_api_usercoursetag` table.
            Retrieved with::
                `dict(
                    UserCourseTag.objects.filter(
                        user=request.user.pk, course_id=course_key
                    ).values_list('key', 'value')
                )`
            Note::
                Is only present when a course page is requested.
                Is an empty dictionary when the user is not logged in or not found in
                the `user_api_usercoursetag` table.
        user_id (int or str or None): Consists of the ID of the authenticated user.
            Retrieved with::
                `request.user.pk` querying the `auth_user` table.
            Note::
                Is an integer when the user is found in the `auth_user` table.
                Is an empty string when an exception is raised while retrieving the ID.
                Is `None` when the user is not logged in.
        org_id (str): Consists of the organization name that lists the course.
            Retrieved with::
                `course_id.org` where `course_id` is an
                `opaque_keys.edx.locator.CourseLocator` which is created using the URL
                of the requested page.
            Note::
                Is an empty string when the requested page is not a course page.
        course_id (str): Consists of the unique identifier for the visited course page.
            Retrieved with::
                `course_id.to_deprecated_string()` where `course_id` is an
                `opaque_keys.edx.locator.CourseLocator` which is created using the URL
                of the requested page.
            Note::
                Is an empty string when the requested page is not a course page.
        path (Path): Consist of the relative URL (without the hostname) of the
            requested page.
            Retrieved with::
                `request.META['PATH_INFO']`
    """

    course_id: constr(regex=r"^$|^course-v1:.+\+.+\+.+$")
    course_user_tags: Optional[Dict[str, str]]
    module: Optional[ContextModuleField]
    org_id: str
    path: Path
    user_id: Union[int, Literal[""], None]


class AbstractBaseEventField(BaseModelWithConfig):
    """Pydantic model for core `event` fields.

    The base model does not have any attributes as event field does not have common
    sub-fields.
    """


class BaseEdxModel(BaseModelWithConfig):
    """Pydantic model for core statement.

    WARNING: it does not define the `event`, `event_type` and `event_source` fields.

    Attributes:
        username (str): Consists of the unique username identifying the logged-in user.
            Retrieved with::
                `request.user.username` querying the `auth_user` table.
            Note::
                Is an empty string when the user is not logged in.
                If an exception is raised when retrieving the username from the table
                then the value is `anonymous`.
                Usernames are made of 2-30 ASCII letters / numbers / underscores (_) /
                hyphens (-)
        ip (IPv4Address or str): Consists of the public IPv4 address of the user.
            Retrieved with::
                `get_ip(request)` cf. https://github.com/un33k/django-ipware/tree/1.1.0
            Note::
                Can be an empty string if the IP address is not found.
        agent (str): Consists of the `User-Agent` HTTP request header.
            Retrieved with::
                `request.META[HTTP_USER_AGENT]`
            Note::
                Can be an empty string if the header is not present in the request.
                Contains information about:
                    Browser name and version
                    Operating system name and version
                    Default language
        host (str): Consists of the hostname of the server.
            Retrieved with::
                `request.META[SERVER_NAME]`
        referer (Path): Consists of the `Referer` HTTP request header.
            Retrieved with::
                `request.META[HTTP_REFERER]`
            Note::
                Can be an empty string if the header is not present in the request.
                Contains the referring URL (previous URL visited by the user).
        accept_language (str): Consists of the `Accept-Language` HTTP request header.
            Retrieved with::
                `request.META[HTTP_ACCEPT_LANGUAGE]`
            Note::
                Can be an empty string if the header is not present in the request.
                Contains the default language settings of the user.
        context (BaseContextField): see BaseContextField.
        time (datetime): Consists of the UTC time in ISO format at which the event was
            emitted.
            Retrieved with::
                `datetime.datetime.utcnow()`
        page (None): Consists of the value `None`
            Note::
                In JSON the value is `null` instead of `None`.
    """

    username: Union[constr(min_length=2, max_length=30), Literal[""]]
    ip: Union[IPv4Address, Literal[""]]
    agent: str
    host: str
    referer: Union[AnyHttpUrl, Literal[""]]
    accept_language: str
    context: BaseContextField
    time: datetime
    page: None
