"""Definition of the Landmark class to handle visitable objects across the world."""
from typing import Optional, Literal
from uuid import uuid4, UUID
from pydantic import BaseModel, Field


# Output to frontend
class Landmark(BaseModel) :
    """
    A class representing a landmark or point of interest (POI) in the context of a trip.

    The Landmark class is used to model visitable locations, such as tourist attractions,
    natural sites, shopping locations, and start/end points in travel itineraries. It
    holds information about the landmark's attributes and supports comparisons and
    calculations, such as distance between landmarks.

    Attributes:
        name (str): The name of the landmark.
        type (Literal): The type of the landmark, which can be one of ['sightseeing', 'nature', 
            'shopping', 'start', 'finish'].
        location (tuple): A tuple representing the (latitude, longitude) of the landmark.
        osm_type (str): The OpenStreetMap (OSM) type of the landmark.
        osm_id (int): The OpenStreetMap (OSM) ID of the landmark.
        attractiveness (int): A score representing the attractiveness of the landmark.
        n_tags (int): The number of tags associated with the landmark.
        image_url (Optional[str]): A URL to an image of the landmark.
        website_url (Optional[str]): A URL to the landmark's official website.
        description (Optional[str]): A text description of the landmark.
        duration (Optional[int]): The estimated time to visit the landmark (in minutes).
        name_en (Optional[str]): The English name of the landmark.
        uuid (UUID): A unique identifier for the landmark, generated by default using uuid4.
        must_do (Optional[bool]): Whether the landmark is a "must-do" attraction.
        must_avoid (Optional[bool]): Whether the landmark should be avoided.
        is_secondary (Optional[bool]): Whether the landmark is secondary or less important.
        time_to_reach_next (Optional[int]): Estimated time (in minutes) to reach the next landmark.
        next_uuid (Optional[UUID]): UUID of the next landmark in sequence (if applicable).
    """

    # Properties of the landmark
    name : str
    type: Literal['sightseeing', 'nature', 'shopping', 'start', 'finish']
    location : tuple
    osm_type : str
    osm_id : int
    attractiveness : int
    n_tags : int

    # Optional properties to gather more information.
    image_url : Optional[str] = None
    website_url : Optional[str] = None
    wiki_url : Optional[str] = None
    keywords: Optional[dict] = {}
    description : Optional[str] = None
    duration : Optional[int] = 5
    name_en : Optional[str] = None

    # Unique ID of a given landmark
    uuid: UUID = Field(default_factory=uuid4)

    # Additional properties depending on specific tour
    must_do : Optional[bool] = False
    must_avoid : Optional[bool] = False
    is_secondary : Optional[bool] = False

    time_to_reach_next : Optional[int] = 0
    next_uuid : Optional[UUID] = None

    # More properties to define the score
    is_viewpoint : Optional[bool] = False
    is_place_of_worship : Optional[bool] = False


    def __str__(self) -> str:
        """
        String representation of the Landmark object.

        Returns:
            str: A formatted string with the landmark's type, name, location, attractiveness score,
            time to the next landmark (if available), and whether the landmark is secondary.
        """
        t_to_next_str = f", time_to_next={self.time_to_reach_next}" if self.time_to_reach_next else ""
        is_secondary_str = ", secondary" if self.is_secondary else ""
        type_str = '(' + self.type + ')'

        return (f'Landmark{type_str}: [{self.name} @{self.location}, '
                f'score={self.attractiveness}{t_to_next_str}{is_secondary_str}]')

    def distance(self, value: 'Landmark') -> float:
        """
        Calculates the squared distance between this landmark and another.

        Args:
            value (Landmark): Another Landmark object to calculate the distance to.

        Returns:
            float: The squared Euclidean distance between the two landmarks.
        """
        return (self.location[0] - value.location[0])**2 + (self.location[1] - value.location[1])**2

    def __hash__(self) -> int:
        """
        Generates a hash for the Landmark based on its name.

        Returns:
            int: The hash of the landmark.
        """
        return hash(self.name)

    def __eq__(self, value: 'Landmark') -> bool:
        """
        Checks equality between two Landmark objects based on UUID, OSM ID, and name.

        Args:
            value (Landmark): Another Landmark object to compare.

        Returns:
            bool: True if the landmarks are equal, False otherwise.
        """
        # eq and hash must be consistent
        # in particular, if two objects are equal, their hash must be equal
        # uuid and osm_id are just shortcuts to avoid comparing all the properties
        # if they are equal, we know that the name is also equal and in turn the hash is equal
        return (self.uuid == value.uuid or
                self.osm_id == value.osm_id or
                (self.name == value.name and self.distance(value) < 0.001))
