from __future__ import annotations

import enum
from datetime import datetime
from typing import Any, Optional, Union

from pydantic import BaseModel, ConfigDict, Field
from pydantic_extra_types.coordinate import Latitude, Longitude
from pydantic_extra_types.currency_code import Currency


class Mode(str, enum.Enum):
    TRAIN = "train"
    BUS = "bus"
    WATERCRAFT = "watercraft"
    TAXI = "taxi"
    GONDOLA = "gondola"
    AIRCRAFT = "aircraft"
    CAR = "car"
    BICYCLE = "bicycle"
    WALKING = "walking"


class FPTFModel(BaseModel):
    model_config = ConfigDict(extra="allow")


class Location(FPTFModel):
    """
    A location object is used by other items to indicate their locations.

    If the latitude field is specified in a location object, the longitude
    field must also be specified, and vice-versa.
    """

    type: str = Field(default="location", frozen=True)
    name: Optional[str] = None
    address: Optional[str] = None

    longitude: Optional[Longitude] = None
    latitude: Optional[Latitude] = None
    altitude: Optional[float] = None


class Station(FPTFModel):
    """
    A station is a larger building or area that can be identified by a name.
    It is usually represented by a single node on a public transport map.
    Whereas a stop usually specifies a location, a station often is a broader
    area that may span across multiple levels or buildings.
    """

    type: str = Field(default="station", frozen=True)
    id: str
    name: str

    location: Optional[Location] = None
    regions: list[Union[str, Region]]


class Stop(FPTFModel):
    """
    A stop is a single small point or structure at which vehicles stop. A stop
    always belongs to a station. It may for example be a sign, a basic shelter
    or a railway platform.

    If the underlying data source does not allow such a fine-grained
    distinction, use stations instead
    """

    type: str = Field(default="stop", frozen=True)
    id: str
    station: Union[str, Station]
    name: str
    location: Optional[Location] = None


class Region(FPTFModel):
    """
    A region is a group of stations, for example a metropolitan area or a
    geographical or cultural region.

    In many urban areas, there are several long-distance train & bus stations,
    all distinct but well-connected through local public transport. It makes
    sense to keep them as stations, because they may still have individual
    stops, but clustering them enables more advanced routing information.

    A station can be part of multiple regions.
    """

    type: str = Field(default="region", frozen=True)
    id: str
    name: str
    stations: list[Union[str, Station]]


class Line(FPTFModel):
    """
    A single line with regular service represented by individual routes that
    have been run.
    """

    type: str = Field(default="line", frozen=True)
    id: str
    name: str
    mode: Mode
    subMode: Any = None
    routes: list[Union[str, Route]]
    operator: Union[str, Operator]


class Route(FPTFModel):
    """
    A route represents a single set of stations, of a single line.

    For a very consistent subway service, there may be one route for each
    direction. Planned detours, trains stopping early and additional
    directions would have their own route.
    """

    type: str = Field(default="route", frozen=True)
    id: str
    line: Union[str, Line]
    mode: Optional[Mode] = None
    subMode: Any = None
    stops: list[Union[str, Stop, Station]]


class Schedule(FPTFModel):
    """
    Note: There are many ways to format schedules of public transport routes.
    This one tries to balance the amount of data and consumability. It is
    specifically geared towards urban public transport, with frequent trains
    and homogenous travels.
    """

    type: str = Field(default="schedule", frozen=True)
    id: str
    route: Union[str, Route]
    mode: Optional[Mode] = None
    subMode: Any = None
    sequence: list[SequenceStop]
    starts: list[int]


class SequenceStop(FPTFModel):
    """
    seconds relative to departure at first station/stop in 1-to-1 relation to
    `route` stops
    """

    arrival: Optional[int] = None
    departure: Optional[int] = None


class Operator(FPTFModel):
    """ """

    type: str = Field(default="operator", frozen=True)
    id: str
    name: str


class Stopover(FPTFModel):
    """
    A stopover represents a vehicle stopping at a stop/station at a specific
    time.
    """

    type: str = Field(default="stopover", frozen=True)
    stop: Union[str, Stop, Station]

    arrival: Optional[datetime] = None
    arrivalDelay: Optional[int] = None
    arrivalPlatform: Optional[str] = None

    departure: Optional[datetime] = None
    departureDelay: Optional[int] = None
    departurePlatform: Optional[str] = None


class Price(FPTFModel):
    amount: float
    currency: Currency


class Leg(FPTFModel):
    origin: Union[str, Station, Stop, Location]
    destination: Union[str, Station, Stop, Location]

    departure: datetime
    departureDelay: Optional[int] = None
    departurePlatform: Optional[str] = None

    arrival: datetime
    arrivalDelay: Optional[int] = None
    arrivalPlatform: Optional[str] = None

    stopovers: Optional[list[Stopover]] = None
    schedule: Optional[Union[str, Schedule]] = None
    mode: Optional[Mode] = None
    subMode: Any = None
    public: bool

    operator: Optional[Union[str, Operator]] = None
    price: Optional[Price] = None


class Journey(FPTFModel):
    """
    A journey is a computed set of directions to get from A to B at a specific
    time. It would typically be the result of a route planning algorithm.
    """

    type: str = Field(default="journey", frozen=True)
    id: str
    legs: list[Leg]
    price: Optional[Price] = None
