import random
import string
import uuid
from pathlib import Path
from typing import Union
from urllib.parse import ParseResult

from filip.models.ngsi_v2.context import ContextEntity
from filip.models.ngsi_v2.iot import Device, ServiceGroup
from pydantic import BaseModel, ConfigDict, Field, field_validator

from fidere.util import load_json_from_file_or_url


class Identifier(str):
    """A simple alternative for a string. In the future validation of
    ids or other related operations might take place here!"""

    @classmethod
    def from_uuid(cls):
        """create an identifier from an uuid4

        Returns:
            Identifier
        """
        return cls(uuid.uuid4())

    @classmethod
    def from_ascii(cls, length: int = 16):
        """Create an identifier from a random ASCII string of certain length

        Args:
            length (int, optional, default=16): length of the random string

        Returns:
            Identifier
        """
        return cls("".join(random.sample(string.ascii_letters, length)))


class DataModel(BaseModel):
    """This class documents the API endpoints a data model can implement.
    If anything is unclear, a look into already established data models
    will likely help.
    When implementing, it should be safe to assume the given types of each
    method. Since the user should not interact with the data models itself,
    the `JsonSchemaParser` should do any necessary checks!


    Examples:
        Every further data model created should inherit from this class:
        >>> class AwesomeDataModel(DataModel):
        >>>     pass
    """

    schema_location: Union[ParseResult, Path] = Field(frozen=False)
    identifier: Identifier = Field(default=None, frozen=True)
    model_config = ConfigDict(
        validate_assignment=True, arbitrary_types_allowed=True, extra="allow"
    )

    @field_validator("identifier", mode="before")
    def ensure_type(cls, value):
        if not isinstance(value, Identifier):
            value = Identifier(value)
        return value

    def __del__(self):
        """Possibility to run any cleanup after a data model is deleted.
        This could be closing any open connections to servers or deletion
        of temporary files"""
        pass

    def parse(self, **kwargs):
        raise NotImplementedError(
            f"The datamodel {self.__repr_name__()} does "
            "not implement a parse method"
        )

    def instance_device(self, instance: dict, **kwargs) -> Device:
        """This function takes in a dict in key-value format and returns
        a FiLiP Device class instance based on the underlying data model.

        Args:
            instance (dict): Instance in keyvalue representation

        Keyword Args:
            Specific to implemented model
        """
        raise NotImplementedError(
            f"{self.__repr_name__()} does not support instancing a device"
        )

    def instance_entity(self, instance: dict, **kwargs) -> ContextEntity:
        """This function takes in a dict in key-value format and returns
        a FiLiP ContextEntity class instance based on the underlying
        data model.

        Args:
            instance (dict): Instance in keyvalue representation

        Keyword Args:
            Specific to implemented model
        """
        raise NotImplementedError(
            f"{self.__repr_name__()} does not support "
            "instancing a context entity"
        )

    def instance_service_group(self, instance: dict, **kwargs) -> ServiceGroup:
        """This function takes in a dict in key-value format and returns
        a FiLiP ServiceGroup class instance based on the underlying data
        model.

        Args:
            instance (dict): Instance in keyvalue representation

        Keyword Args:
            Specific to implemented model
        """
        raise NotImplementedError(
            f"{self.__repr_name__()} does not support "
            "instancing a service group"
        )

    def validate_entity(self, entity: ContextEntity) -> None:
        """Validates a ContextEntity, likely received from the ContextBroker
        or created a different way.

        Args:
            entity (ContextEntity): entity to be validated

        Raises:
            ValidationError: In case the entity is not valid!
        """
        raise NotImplementedError(
            "Entity validation is not implemented "
            f"by data model {self.__repr_name__()}"
        )

    def validate_device(self, device: Device) -> None:
        """Validates a Device, likely received from the IoT-Agent or
        created a different way.

        Args:
            device (Device): device to be validated

        Raises:
            ValidationError: In case the entity is not valid!
        """
        raise NotImplementedError(
            "Device validation is not implemented "
            f"by data model {self.__repr_name__()}"
        )

    @classmethod
    def with_id_from_schema(cls, schema_location, **kwargs):
        """This functions extracts a unique identifier (likely the "$id'
        field from a JSON-Schema). It then creates an instance of itself

        Args:
            schema_location (Path, ParseResult): Location where the schema
            is located at

        Keyword Args:
            http_headers (dict[str, Any]): Headers that should be used for
                the possible HTTP(S) request
        """
        schema = load_json_from_file_or_url(schema_location, **kwargs)
        return cls(
            schema_location=schema_location,
            identifier=Identifier(schema["$id"]),
        )
