"""This file was originally generated by xsdata, v24.1, on 2024-01-29 15:42:25
+ modified

Generator: DataclassGenerator
See: https://xsdata.readthedocs.io/
"""
import logging
import warnings
from abc import ABC, abstractmethod
from collections import Counter
from dataclasses import dataclass
from datetime import date, timedelta
from enum import Enum
from typing import Optional, Dict, Tuple, List

from eflips.model import VehicleType, Scenario, Rotation, Line, Route, AssocRouteStation, Station

from eflips.ingest.util import get_altitude


class OnrTyp(Enum):
    """
    The type of point in the schedule.
    """

    stop = "HP"
    """
    A bus stop.
    """
    depot_gate = "BHOF"
    """
    The entrance or exit of a depot. Trips that start or end here are (by definition) empty.
    """


class RoutenArt(Enum):
    NORMAL = 1
    TO_DEPOT = 2
    FROM_DEPOT = 3
    ZUFAHRT = 4


@dataclass
class VdvBaseObject(ABC):
    basis_version: int
    """
    The `BasisVerGueltigkeit.basis_version` this object belongs to. Currently not used. 

    Later, it may be a good idea to allow the user to select a version of the schedule to import.

    Restriction: Should be a nine-digit numeric string.
    """

    @property
    @abstractmethod
    def primary_key(self) -> Tuple[int | date | str, ...]:
        """
        The primary key of the object.
        """
        pass

    @classmethod
    @abstractmethod
    def from_dict(cls, data: Dict[str, int | str | float | None]) -> "VdvBaseObject":
        """
        Load the data from a dictionary.
        """
        pass


@dataclass
class VdvBaseObjectWithONR(VdvBaseObject):
    """
    This is a parent class for all objects that have an `onr_typ_nr` attribute.
    """

    onr_typ_nr: int
    """
    This is a freign key to the `ONR_TYP_NR` in the MENGE_ONR_TYP table. The `STR_ONR_TYP` in this table is the type
    of point we are dealing with. 

    We just note the ID down on initializtion, however to be able to use it, we also will need to set the 
    `_onr_type_map` dictionary attribute of this object.

    Restriction: Should be a positive integer.
    """

    # _onr_type_map: Dict[int, OnrTyp]
    """
    A dictionary that maps the `onr_typ_nr` to the `OnrTyp` enum.
    """

    # @property
    # def onr_typ(self) -> OnrTyp:
    #    """
    #    The type of point in the schedule.
    #    """
    #    return self._onr_type_map[self.onr_typ_nr]

    ort_nr: int
    """
    This seems to be a dupllicate of `onr_typ_nr`. We do store it, but we do not use it.

    Restriction: Should be a positive integer.
    """


@dataclass
class BasisVerGueltigkeit(VdvBaseObject):
    """
    The dataclass corresponding to BASIS_VER_GUELTIGKEIT (993) in the VDV-452 specification.

    It allows different versions of the schedule to be stored in the same database. It is *not implemented* in the curent
    importer, we only allow there to be a single version of the schedule in the database.

    In a later version, it may be a good idea to allow the user to select a version of the schedule to import.


    """

    ver_gueltigkeit: date
    """
    The date after which the schedule is valid.
    """

    @classmethod
    def from_dict(cls, data: Dict[str, str | int | float | None]) -> "BasisVerGueltigkeit":
        """
        Load the data from a dictionary.
        :param data: A dictionary with the data. It should contain:
            - "ver_gueltigkeit": The date after which the schedule is valid. in the format "YYYYMMDD".
            - "basis_version": A nine-digit numeric string that identifies the version of the schedule.
        :return: The object itself.

        """
        assert isinstance(data["VER_GUELTIGKEIT"], int), "The `ver_gueltigkeit` should be an integer."
        year = data["VER_GUELTIGKEIT"] // 10000
        month = (data["VER_GUELTIGKEIT"] % 10000) // 100
        day = data["VER_GUELTIGKEIT"] % 100

        assert isinstance(data["BASIS_VERSION"], int), "The `basis_version` should be an integer."

        return BasisVerGueltigkeit(ver_gueltigkeit=date(year, month, day), basis_version=data["BASIS_VERSION"])

    @property
    def primary_key(self) -> Tuple[int | date | str, ...]:
        """
        The primary key of the object. Used e.g. to store it in a dictionary.
        :return:
        """
        return (self.ver_gueltigkeit,)

    def __eq__(self: "BasisVerGueltigkeit", other: object) -> bool:
        if not isinstance(other, BasisVerGueltigkeit):
            return False
        return self.ver_gueltigkeit == other.ver_gueltigkeit and self.basis_version == other.basis_version

    def __hash__(self: "BasisVerGueltigkeit") -> int:
        return hash((self.ver_gueltigkeit, self.basis_version))


@dataclass(kw_only=True)
class Firmenkalender(VdvBaseObject):
    """
    This class corresponds to the FIRMENKALENDER (384) in the VDV-452 specification.

    It apparently associates a calendar day with a type of day.

    """

    betriebstag: date
    """
    The actual date of the day.
    """

    tagesart_nr: int
    """
    The type of day this is.
    """

    @classmethod
    def from_dict(cls, data: Dict[str, str | int | float | None]) -> "Firmenkalender":
        """
        Load the data from a dictionary.
        :param data: A dictionary with the data. It should contain:
            - "BASIS_VERSION": The `BasisVerGueltigkeit.basis_version` this object belongs to. Currently not used.
            - "BETRIEBSTAG": The actual date of the day in the format "YYYYMMDD".
            - "TAGESART_NR": The type of day this is.
        :return: The object itself.
        """
        assert isinstance(data["BETRIEBSTAG"], int), "The `betriebstag` should be an integer."
        year = data["BETRIEBSTAG"] // 10000
        month = (data["BETRIEBSTAG"] % 10000) // 100
        day = data["BETRIEBSTAG"] % 100

        assert isinstance(data["BASIS_VERSION"], int), "The `basis_version` should be an integer."
        assert isinstance(data["TAGESART_NR"], int), "The `tagesart_nr` should be an integer."

        return Firmenkalender(
            basis_version=data["BASIS_VERSION"],
            betriebstag=date(year, month, day),
            tagesart_nr=data["TAGESART_NR"],
        )

    @property
    def primary_key(self) -> Tuple[int | date | str, ...]:
        """
        The primary key of the object. Used e.g. to store it in a dictionary.
        :return:
        """
        return self.basis_version, self.betriebstag


@dataclass
class LidVerlauf(VdvBaseObjectWithONR):
    """
    The dataclass corresponding to LID_VERLAUF (246) in the VDV-452 specification.

    It represents a point during a line, corresponding (loosely) to an AssocRouteStation object in the eflips-model
    world.
    """

    @property
    def position_key(self) -> Tuple[int, int, int]:
        """
        This key can be used to identify the station by its primary key.
        """
        return self.basis_version, self.onr_typ_nr, self.ort_nr

    li_lfd_nr: int
    """
    The ordinal number of the line this object belongs to. 
    
    Restriction: Should be a positive integer.
    """

    li_nr: int
    """
    The "line number", which most closely corresponds to the `Line.name` in the eflips-model world.
    
    Restriction: Should be a six-character string.
    """

    str_li_var: str
    """
    An identifier for the line variant. TODO: Unclear at this point whether this is a route name?
    
    Restriction: Should be a six-character string.
    """

    @property
    def primary_key(self) -> Tuple[int | date | str, ...]:
        """
        The primary key of the object. Used e.g. to store it in a dictionary.
        :return:
        """
        return self.basis_version, self.li_nr, self.str_li_var, self.li_lfd_nr

    @classmethod
    def from_dict(cls, data: Dict[str, str | int | float | None]) -> "LidVerlauf":
        """
        Load the data from a dictionary.
        :param data: A dictionary with the data. It should contain:
            - "basis_version": The `BasisVerGueltigkeit.basis_version` this object belongs to. Currently not used.
            - "li_lfd_nr": The ordinal number of the line this object belongs to.
            - "li_nr": The "line number", which most closely corresponds to the `Line.name` in the eflips-model world.
            - "str_li_var": An identifier for the line variant. TODO: Unclear at this point whether this is a route name?
            - "onr_typ_nr": This is a freign key to the `ONR_TYP_NR` in the MENGE_ONR_TYP table. The `STR_ONR_TYP` in this table is the type
                of point we are dealing with.
            - "ort_nr": This seems to be a dupllicate of `onr_typ_nr`. We do store it, but we do not use it.
            - "produktiv": This is a flag that indicates whether the trip is a productive trip -> TripType.PASSENGER in eflips-model world or
                a non-productive trip -> TripType.EMPTY in eflips-model world.
        :return: The object itself.
        """

        assert isinstance(data["LI_LFD_NR"], int), "The `li_lfd_nr` should be an integer."
        assert data["LI_LFD_NR"] > 0, "The `li_lfd_nr` should be a positive integer."
        assert isinstance(data["LI_NR"], int), "The `li_nr` should be an integer."
        assert isinstance(data["STR_LI_VAR"], str), "The `str_li_var` should be a string."

        assert isinstance(data["ONR_TYP_NR"], int), "The `onr_typ_nr` should be an integer."
        assert data["ONR_TYP_NR"] > 0, "The `onr_typ_nr` should be a positive integer."
        assert isinstance(data["ORT_NR"], int), "The `ort_nr` should be an integer."
        assert data["ORT_NR"] > 0, "The `ort_nr` should be a positive integer."

        assert isinstance(data["BASIS_VERSION"], int), "The `basis_version` should be an integer."

        return LidVerlauf(
            basis_version=data["BASIS_VERSION"],
            li_lfd_nr=data["LI_LFD_NR"],
            li_nr=data["LI_NR"],
            str_li_var=data["STR_LI_VAR"],
            onr_typ_nr=data["ONR_TYP_NR"],
            ort_nr=data["ORT_NR"],
        )


@dataclass
class OrtHztf(VdvBaseObjectWithONR):
    """
    ORT_HZTF (999) Stores the dwell duration at a stop for a trip. It is most equivalent to the `StopTime.dwell_duration`
    """

    fgr_nr: int
    """The number of the timing group. Part of primary key."""

    @property
    def position_key(self) -> Tuple[int, int, int]:
        return self.basis_version, self.onr_typ_nr, self.ort_nr

    hp_hzt: timedelta
    """The time the trip waits at the stop in seconds."""

    @property
    def primary_key(self) -> Tuple[int | date | str, ...]:
        return self.basis_version, self.fgr_nr, self.onr_typ_nr, self.ort_nr

    @classmethod
    def from_dict(cls, data: Dict[str, str | int | float | None]) -> "OrtHztf":
        assert isinstance(data["BASIS_VERSION"], int), "The `basis_version` should be an integer."
        assert isinstance(data["ONR_TYP_NR"], int), "The `onr_typ_nr` should be an integer."
        assert isinstance(data["ORT_NR"], int), "The `ort_nr` should be an integer."
        assert isinstance(data["FGR_NR"], int), "The `fgr_nr` should be an integer."
        assert isinstance(data["HP_HZT"], int), "The `hp_hzt` should be an integer."

        return OrtHztf(
            basis_version=data["BASIS_VERSION"],
            onr_typ_nr=data["ONR_TYP_NR"],
            ort_nr=data["ORT_NR"],
            fgr_nr=data["FGR_NR"],
            hp_hzt=timedelta(seconds=data["HP_HZT"]),
        )


@dataclass
class RecFrtHzt(VdvBaseObjectWithONR):
    """

    REC_FRT_HZT (308) stores the waiting time at a stop for a trip. It is most equivalent to the
    `StopTime.dwell_duration`

    """

    frt_fid: int
    """The Identifier of the trip. Part of primary key."""

    @property
    def position_key(self) -> Tuple[int, int, int]:
        return self.basis_version, self.onr_typ_nr, self.ort_nr

    @property
    def primary_key(self) -> Tuple[int | date | str, ...]:
        return self.basis_version, self.frt_fid, self.onr_typ_nr, self.ort_nr

    frt_hzt_zeit: timedelta
    """The time the trip waits at the stop in seconds."""

    @classmethod
    def from_dict(cls, data: Dict[str, str | int | float | None]) -> "RecFrtHzt":
        assert isinstance(data["BASIS_VERSION"], int), "The `basis_version` should be an integer."
        assert isinstance(data["ONR_TYP_NR"], int), "The `onr_typ_nr` should be an integer."
        assert isinstance(data["ORT_NR"], int), "The `ort_nr` should be an integer."
        assert isinstance(data["FRT_FID"], int), "The `frt_fid` should be an integer."
        assert isinstance(data["FRT_HZT_ZEIT"], int), "The `frt_hzt_zeit` should be an integer."

        return RecFrtHzt(
            basis_version=data["BASIS_VERSION"],
            onr_typ_nr=data["ONR_TYP_NR"],
            ort_nr=data["ORT_NR"],
            frt_fid=data["FRT_FID"],
            frt_hzt_zeit=timedelta(seconds=data["FRT_HZT_ZEIT"]),
        )


@dataclass
class SelFztFeld(VdvBaseObjectWithONR):
    """

    SEL_FZT_FELD (282) stores the timing information for a segment of a route. It is a part of a route.

    """

    bereich_nr: int
    """
    The identifier of the area. Part of primary key.
    """

    fgr_nr: int
    """
    The name of the timing group number this trip belongs to. Used to look up the timing group.
    """

    @property
    def start_station_primary_key(self) -> Tuple[int, int, int]:
        """
        This key can be used to identify the start station by its primary key.
        """
        return self.basis_version, self.onr_typ_nr, self.ort_nr

    sel_ziel: int
    """
    The identifier of the target point.
    """

    sel_ziel_typ: int
    """
    The type of the target point.
    """

    @property
    def end_station_primary_key(self) -> Tuple[int, int, int]:
        """
        This key can be used to identify the start station by its primary key.
        """
        return self.basis_version, self.sel_ziel_typ, self.sel_ziel

    sel_fzt: timedelta
    """
    The time it takes to travel from the start to the end in seconds.
    """

    @property
    def primary_key(self) -> Tuple[int | date | str, ...]:
        return (
            self.basis_version,
            self.bereich_nr,
            self.fgr_nr,
            self.onr_typ_nr,
            self.ort_nr,
            self.sel_ziel_typ,
            self.sel_ziel,
        )

    @classmethod
    def from_dict(cls, data: Dict[str, str | int | float | None]) -> "SelFztFeld":
        assert isinstance(data["BASIS_VERSION"], int), "The `basis_version` should be an integer."
        assert isinstance(data["BEREICH_NR"], int), "The `bereich_nr` should be an integer."
        assert isinstance(data["FGR_NR"], int), "The `fgr_nr` should be an integer."
        assert isinstance(data["ONR_TYP_NR"], int), "The `onr_typ_nr` should be an integer."
        assert isinstance(data["ORT_NR"], int), "The `ort_nr` should be an integer."
        assert isinstance(data["SEL_ZIEL_TYP"], int), "The `sel_ziel_typ` should be an integer."
        assert isinstance(data["SEL_ZIEL"], int), "The `sel_ziel` should be an integer."
        assert isinstance(data["SEL_FZT"], int), "The `sel_fzt` should be an integer."

        return SelFztFeld(
            basis_version=data["BASIS_VERSION"],
            bereich_nr=data["BEREICH_NR"],
            fgr_nr=data["FGR_NR"],
            onr_typ_nr=data["ONR_TYP_NR"],
            ort_nr=data["ORT_NR"],
            sel_ziel_typ=data["SEL_ZIEL_TYP"],
            sel_ziel=data["SEL_ZIEL"],
            sel_fzt=timedelta(seconds=data["SEL_FZT"]),
        )


@dataclass
class UebFzt(VdvBaseObjectWithONR):
    """

    This is a timing specific for empty trips. It is a part of a route.

    """

    bereich_nr: int
    """
    An identifier of the area. Part of primary key.    
    """

    fgr_nr: int
    """
    To which timing group this belongs.
    """

    @property
    def start_station_primary_key(self) -> Tuple[int, int, int]:
        """
        This key can be used to identify the start station by its primary key.
        """
        return self.basis_version, self.onr_typ_nr, self.ort_nr

    ueb_ziel_typ: int
    """
    The type of the target point. Part of primary key.
    """

    ueb_ziel: int
    """
    The identifier of the target point. Part of primary key.
    """

    ueb_fahrzeit: timedelta
    """
    The time it takes to travel from the start to the end in seconds.
    """

    @property
    def primary_key(self) -> Tuple[int | date | str, ...]:
        return (
            self.basis_version,
            self.bereich_nr,
            self.fgr_nr,
            self.onr_typ_nr,
            self.ort_nr,
            self.ueb_ziel_typ,
            self.ueb_ziel,
        )

    @classmethod
    def from_dict(cls, data: Dict[str, str | int | float | None]) -> "UebFzt":
        assert isinstance(data["BASIS_VERSION"], int), "The `basis_version` should be an integer."
        assert isinstance(data["BEREICH_NR"], int), "The `bereich_nr` should be an integer."
        assert isinstance(data["FGR_NR"], int), "The `fgr_nr` should be an integer."
        assert isinstance(data["ONR_TYP_NR"], int), "The `onr_typ_nr` should be an integer."
        assert isinstance(data["ORT_NR"], int), "The `ort_nr` should be an integer."
        assert isinstance(data["UEB_ZIEL_TYP"], int), "The `ueb_ziel_typ` should be an integer."
        assert isinstance(data["UEB_ZIEL"], int), "The `ueb_ziel` should be an integer."
        assert isinstance(data["UEB_FAHRZEIT"], int), "The `ueb_fahrzeit` should be an integer."

        return UebFzt(
            basis_version=data["BASIS_VERSION"],
            bereich_nr=data["BEREICH_NR"],
            fgr_nr=data["FGR_NR"],
            onr_typ_nr=data["ONR_TYP_NR"],
            ort_nr=data["ORT_NR"],
            ueb_ziel_typ=data["UEB_ZIEL_TYP"],
            ueb_ziel=data["UEB_ZIEL"],
            ueb_fahrzeit=timedelta(seconds=data["UEB_FAHRZEIT"]),
        )


@dataclass()
class RecFrt(VdvBaseObject):
    """
    This seems to be the equivalent of a trip in the eflips-model world. It is a trip that is part of a line.
    """

    frt_fid: int
    """
    The unique identifier of the trip. This is the primary key of the table.
    """

    frt_start: timedelta
    """
    The start time of the trip. Expressed as an offset from midnight in the "Europe/Berlin" timezone.
    """

    li_nr: int
    """
    The line name this trip belongs to.
    """

    tagesart_nr: int
    """
    A number of the type of day this trip is scheduled for.
    """

    fahrtart_nr: int
    """
    The type of trip this is. We simpilfy to a boolean in the eflips-model world.
    """

    fgr_nr: int
    """
    The name of the timing group number this trip belongs to. Used to look up the timing group.
    """

    str_li_var: str
    """
    An identifier for the line variant. TODO: Unclear at this point whether this is a route name?
    """

    um_uid: int
    """
    A unique identifier of the rotation this trip belongs to.
    """

    @property
    def primary_key(self) -> Tuple[int | date | str, ...]:
        return self.basis_version, self.frt_fid

    @classmethod
    def from_dict(cls, data: Dict[str, str | int | float | None]) -> "RecFrt":
        """
        Load the data from a dictionary.

        :param data: A dictionary with the following keys:
            - "BASE_VERSION": The `BasisVerGueltigkeit.basis_version` this object belongs to. Currently not used.
            - "FRT_FID": The unique identifier of the trip. This is the primary key of the table.
            - "FRT_START": The start time of the trip. Expressed as an offset from midnight in the "Europe/Berlin" timezone.
            - "LI_NR": The line name this trip belongs to.
            - "TAGESART_NR": A number of the type of day this trip is scheduled for.
            - "FAHRTART_NR": The type of trip this is. We simpilfy to a boolean in the eflips-model world.
            - "FGR_NR": The name of the timing group number this trip belongs to. Used to look up the timing group.
            - "STR_LI_VAR": An identifier for the line variant. TODO: Unclear at this point whether this is a route name?
            - "UM_UID": A unique identifier of the rotation this trip belongs to.
        :return: The object itself.
        """

        assert isinstance(data["FRT_FID"], int), "The `frt_fid` should be an integer."
        assert data["FRT_FID"] > 0, "The `frt_fid` should be a positive integer."
        assert isinstance(data["FRT_START"], int), "The `frt_start` should be an integer."
        assert data["FRT_START"] >= 0, "The `frt_start` should be a positive integer."
        assert isinstance(data["LI_NR"], int), "The `li_nr` should be an integer."
        assert data["LI_NR"] > 0, "The `li_nr` should be a positive integer."
        assert isinstance(data["TAGESART_NR"], int), "The `tagesart_nr` should be an integer."
        assert data["TAGESART_NR"] > 0, "The `tagesart_nr` should be a positive integer."
        assert isinstance(data["FAHRTART_NR"], int), "The `fahrtart_nr` should be an integer."
        assert data["FAHRTART_NR"] > 0, "The `fahrtart_nr` should be a positive integer."
        assert isinstance(data["FGR_NR"], int), "The `fgr_nr` should be an integer."
        assert data["FGR_NR"] > 0, "The `fgr_nr` should be a positive integer."
        assert isinstance(data["STR_LI_VAR"], str), "The `str_li_var` should be a six-character string."
        assert isinstance(data["UM_UID"], int), "The `um_uid` should be an integer."
        assert data["UM_UID"] > 0, "The `um_uid` should be a positive integer."

        assert isinstance(data["BASIS_VERSION"], int), "The `basis_version` should be an integer."

        return RecFrt(
            basis_version=data["BASIS_VERSION"],
            frt_fid=data["FRT_FID"],
            frt_start=timedelta(seconds=data["FRT_START"]),
            li_nr=data["LI_NR"],
            tagesart_nr=data["TAGESART_NR"],
            fahrtart_nr=data["FAHRTART_NR"],
            fgr_nr=data["FGR_NR"],
            str_li_var=data["STR_LI_VAR"],
            um_uid=data["UM_UID"],
        )


@dataclass()
class RecOrt(VdvBaseObjectWithONR):
    """
    A place. For us, it's mainly interesting if we turn it into a Station object.
    """

    ort_name: str
    """
    The name of the point. Equivalent to `Station.name` in the eflips-model world.
    """

    ort_ref_ort: Optional[int]
    """
    The identifier of the parent point. If it is exists, this is the "Station", whereas the object itself
    is only used for the coordinates in the RouteStation object.
    """

    ort_ref_ort_typ: Optional[int]
    """
    The type of the parent point. needed to look it up, since it's part of the primary key.
    """

    ort_ref_ort_kuerzel: Optional[str]
    """
    The short name of the parent station.
    """

    ort_ref_ort_name: Optional[str]
    """
    The name of the parent station.
    """

    latitude: Optional[float]
    """
    The latitude of the point.
    """

    longitude: Optional[float]
    """
    The longitude of the point.
    """

    altitude: Optional[float]
    """
    The altitude of the point.
    """

    @property
    def parent_station_primary_key(self) -> Tuple[int, int, int]:
        assert self.ort_ref_ort is not None, "The `ort_ref_ort` cannot be none for this property."
        assert self.ort_ref_ort_typ is not None, "The `ort_ref_ort_typ` cannot be none for this property."
        return self.basis_version, self.ort_ref_ort, self.ort_ref_ort_typ

    @property
    def primary_key(self) -> Tuple[int | date | str, ...]:
        return self.basis_version, self.onr_typ_nr, self.ort_nr

    @classmethod
    def from_dict(cls, data: Dict[str, str | int | float | None]) -> "RecOrt":
        """
        Load the data from a dictionary.
        """

        if "WGS_XKOOR" in data:
            assert isinstance(data["WGS_XKOOR"], float), "The `WGS_XKOOR` should be a float."
            assert data["WGS_XKOOR"] != 0, "The `WGS_XKOOR` should not be 0."
            longitude: Optional[float] = data["WGS_XKOOR"]
        elif "ORT_POS_LAENGE" in data:
            # According to spec: Latitude in WGS 84 Format:
            # gggmmssnnn (Gradzahl, Minuten,
            # Sekunden mit 3
            # Nachkommastellen, Kein
            # Vorzeichen (+) heißt n.B. (nördliche
            # Breite)
            # Ein Minuszeichen (-) heißt s.B.
            # (südliche Breite))

            warnings.warn("Untested code.")

            assert isinstance(data["ORT_POS_LAENGE"], int), "The `ORT_POS_LAENGE` should be an integer."
            assert data["ORT_POS_LAENGE"] is not None, "The `ORT_POS_LAENGE` should not be None."
            assert data["ORT_POS_LAENGE"] != 0, "The `ORT_POS_LAENGE` should not be 0."
            longitude_seconds = data["ORT_POS_LAENGE"] % 100000 / 1000
            longitude_minutes = (data["ORT_POS_LAENGE"] % 10000000) // 100000
            longitude_degrees = data["ORT_POS_LAENGE"] // 10000000
            longitude = longitude_degrees + longitude_minutes / 60 + longitude_seconds / 3600
        elif "ORT_POS_X" in data:
            # This seems to be longitude * 1e6
            assert isinstance(data["ORT_POS_X"], int), "The `ORT_POS_X` should be an integer."
            longitude = data["ORT_POS_X"] / 1e6
        else:
            longitude = None

        if "WGS_YKOOR" in data:
            assert isinstance(data["WGS_YKOOR"], float), "The `WGS_YKOOR` should be a float."
            assert data["WGS_YKOOR"] != 0, "The `WGS_YKOOR` should not be 0."
            latitude: Optional[float] = data["WGS_YKOOR"]
        elif "ORT_POS_BREITE" in data:
            # According to Spec:
            # Longitude in WGS 84 Format:
            # gggmmssnnn (Gradzahl, Minuten,
            # Sekunden mit 3
            # Nachkommastellen, Kein
            # Vorzeichen (+) heißt ö.L. (östliche
            # Länge)
            # Ein Minuszeichen (-) heißt w.L.
            # (westliche Länge))

            warnings.warn("Untested code.")

            assert isinstance(data["ORT_POS_BREITE"], int), "The `ORT_POS_BREITE` should be an integer."
            assert data["ORT_POS_BREITE"] is not None, "The `ORT_POS_BREITE` should not be None."
            assert data["ORT_POS_BREITE"] != 0, "The `ORT_POS_BREITE` should not be 0."
            latitude_seconds = data["ORT_POS_BREITE"] % 100000 / 1000
            latitude_minutes = (data["ORT_POS_BREITE"] % 10000000) // 100000
            latitude_degrees = data["ORT_POS_BREITE"] // 10000000
            latitude = latitude_degrees + latitude_minutes / 60 + latitude_seconds / 3600
        elif "ORT_POS_Y" in data:
            # This seems to be latitude * 1e6
            assert isinstance(data["ORT_POS_Y"], int), "The `ORT_POS_Y` should be an integer."
            latitude = data["ORT_POS_Y"] / 1e6
        else:
            latitude = None

        if "ORT_POS_HOEHE" in data:
            assert isinstance(data["ORT_POS_HOEHE"], int), "The `ORT_POS_HOEHE` should be an integer."
            altitude: int | None = data["ORT_POS_HOEHE"]
        else:
            if latitude is not None and longitude is not None:
                altitude = int(round(get_altitude((latitude, longitude))))
            else:
                altitude = None

        assert isinstance(data["BASIS_VERSION"], int), "The `basis_version` should be an integer."
        assert isinstance(data["ONR_TYP_NR"], int), "The `onr_typ_nr` should be an integer."
        assert isinstance(data["ORT_NR"], int), "The `ort_nr` should be an integer."
        assert isinstance(data["ORT_NAME"], str), "The `ort_name` should be a string."
        assert isinstance(data["ORT_REF_ORT"], int), "The `ort_ref_ort` should be an integer."
        assert isinstance(data["ORT_REF_ORT_TYP"], int), "The `ort_ref_ort_typ` should be an integer."
        if data["ORT_REF_ORT_KUERZEL"] is not None:
            assert isinstance(data["ORT_REF_ORT_KUERZEL"], str), "The `ort_ref_ort_kuerzel` should be a string."
            ort_ref_ort_kuerzel = data["ORT_REF_ORT_KUERZEL"]
        else:
            ort_ref_ort_kuerzel = None
        assert isinstance(data["ORT_REF_ORT_NAME"], str), "The `ort_ref_ort_name` should be a string."

        return RecOrt(
            basis_version=data["BASIS_VERSION"],
            onr_typ_nr=data["ONR_TYP_NR"],
            ort_nr=data["ORT_NR"],
            ort_name=data["ORT_NAME"],
            ort_ref_ort=data["ORT_REF_ORT"],
            ort_ref_ort_typ=data["ORT_REF_ORT_TYP"],
            ort_ref_ort_kuerzel=data["ORT_REF_ORT_KUERZEL"],
            ort_ref_ort_name=data["ORT_REF_ORT_NAME"],
            latitude=latitude,
            longitude=longitude,
            altitude=altitude,
        )

    @classmethod
    def list_of_stations(
        cls, rec_orts: List["RecOrt"], scenario: Scenario
    ) -> Dict[Tuple[int | date | str, ...], Station]:
        """
        Extract all the stations from the RecOrt objects. They are the "ref_ort" places, that only exist by
        reference to the actual stops
        :param rec_orts: A list of RecOrt objects.
        :return: A Dict of stations, by their VDV primary key. The dict may contain the same station multiple times.
        """

        rec_orts_by_ref_ort: Dict[int, List["RecOrt"]] = {}
        for rec_ort in rec_orts:
            assert rec_ort.ort_ref_ort is not None, "The `ort_ref_or` should not be None."
            if rec_ort.ort_ref_ort not in rec_orts_by_ref_ort:
                rec_orts_by_ref_ort[rec_ort.ort_ref_ort] = []
            rec_orts_by_ref_ort[rec_ort.ort_ref_ort].append(rec_ort)

        stations: Dict[Tuple[int | date | str, ...], Station] = {}
        # For each station (the stops that share the same rec_ort_kuerzel) we create an eflip-model Station object
        for rec_ort_ref_ort, rec_orts in rec_orts_by_ref_ort.items():
            if rec_orts[0].ort_ref_ort_name is not None:
                name = rec_orts[0].ort_ref_ort_name
            else:
                # The name is the most used one of the ort_name fields
                # Count how many times each name appears
                name_counts = Counter([rec_ort.ort_name for rec_ort in rec_orts])
                name = name_counts.most_common(1)[0][0]

            if (
                all([rec_ort.latitude is not None for rec_ort in rec_orts])
                and all([rec_ort.longitude is not None for rec_ort in rec_orts])
                and all([rec_ort.altitude is not None for rec_ort in rec_orts])
            ):
                # The latitude and longitude are the average of the coordinates
                latitude = sum([rec_ort.latitude for rec_ort in rec_orts if rec_ort.latitude is not None]) / len(
                    rec_orts
                )
                longitude = sum([rec_ort.longitude for rec_ort in rec_orts if rec_ort.longitude is not None]) / len(
                    rec_orts
                )
                # The altitude is the average of the altitudes
                altitude = sum([rec_ort.altitude for rec_ort in rec_orts if rec_ort.altitude is not None]) / len(
                    rec_orts
                )

                geom = f"POINT({longitude} {latitude} {altitude})"
            else:
                geom = None

            # The short name is the kuezel field, if it exists
            if rec_orts[0].ort_ref_ort_kuerzel is not None:
                short_name = rec_orts[0].ort_ref_ort_kuerzel
            else:
                short_name = None

            station = Station(scenario=scenario, name=name, name_short=short_name, geom=geom, is_electrified=False)

            for rec_ort in rec_orts:
                stations[rec_ort.primary_key] = station

        return stations


@dataclass()
class RecSel(VdvBaseObjectWithONR):
    """

    This is the segment of a route between two stops. It is a part of a route. The route's distance can be calculated
    from it.

    """

    bereich_nr: int
    """
    An identifier of the area. Part of primary key.
    """

    @property
    def start_station_primary_key(self) -> Tuple[int, int, int]:
        """
        This key can be used to identify the start station by its primary key.
        """
        return self.basis_version, self.onr_typ_nr, self.ort_nr

    sel_ziel: int
    """
    The identifier of the target point.
    """

    sel_ziel_typ: int
    """
    The type of the target point.
    """

    @property
    def end_station_primary_key(self) -> Tuple[int, int, int]:
        """
        This key can be used to identify the start station by its primary key.
        """
        return self.basis_version, self.sel_ziel_typ, self.sel_ziel

    sel_laenge: Optional[int]
    """
    The length of the element in meters.
    """

    @property
    def primary_key(self) -> Tuple[int | date | str, ...]:
        return (
            self.basis_version,
            self.bereich_nr,
            self.onr_typ_nr,
            self.ort_nr,
            self.sel_ziel,
            self.sel_ziel_typ,
        )

    @classmethod
    def from_dict(cls, data: Dict[str, str | int | float | None]) -> "RecSel":
        """
        Load the data from a dictionary.

        :param data: A dictionary with the following keys:
        - "BASIS_VERSION": The `BasisVerGueltigkeit.basis_version` this object belongs to. Currently not used.
        - "BEREICH_NR": An identifier of the area. Unclear what this is used for.
        - "ONR_TYP_NR": This is a freign key to the `ONR_TYP_NR` in the MENGE_ONR_TYP table. The `STR_ONR_TYP` in this table is the type
            of point we are dealing with.
        - "ORT_NR": This seems to be a dupllicate of `onr_typ_nr`. We do store it, but we do not use it.
        - "SEL_ZIEL": The identifier of the target point.
        - "SEL_ZIEL_TYP": The type of the target point.
        - "SEL_LAENGE": The length of the element in meters.

        :return: The object itself.
        """

        assert isinstance(data["BASIS_VERSION"], int), "The `basis_version` should be an integer."
        assert isinstance(data["BEREICH_NR"], int), "The `bereich_nr` should be an integer."
        assert isinstance(data["ONR_TYP_NR"], int), "The `onr_typ_nr` should be an integer."
        assert isinstance(data["ORT_NR"], int), "The `ort_nr` should be an integer."
        assert isinstance(data["SEL_ZIEL_TYP"], int), "The `sel_ziel_typ` should be an integer."
        assert isinstance(data["SEL_ZIEL"], int), "The `sel_ziel` should be an integer."
        assert isinstance(data["SEL_LAENGE"], int), "The `sel_laenge` should be an integer."

        return RecSel(
            basis_version=data["BASIS_VERSION"],
            bereich_nr=data["BEREICH_NR"],
            onr_typ_nr=data["ONR_TYP_NR"],
            ort_nr=data["ORT_NR"],
            sel_ziel=data["SEL_ZIEL"],
            sel_ziel_typ=data["SEL_ZIEL_TYP"],
            sel_laenge=data["SEL_LAENGE"],
        )


@dataclass
class RecUeb(VdvBaseObject):
    """
    REC_UEB (225) stores "Überlaäuferfahrten", which afaik are what we would call empty trips.

    So this is a route, on which only an empty trip is possible.

    """

    bereich_nr: int
    """
    Identifies a branch. Part of primary key.
    """

    onr_typ_nr: int
    """
    Type of the departure point. Part of primary key.
    """

    ort_nr: int
    """
    Identifier of the departure point. Part of primary key.
    """

    @property
    def start_station_primary_key(self) -> Tuple[int, int, int]:
        """
        This key can be used to identify the start station by its primary key.
        """
        return self.basis_version, self.onr_typ_nr, self.ort_nr

    ueb_ziel_typ: int
    """
    Type of the target point. Part of primary key.
    """

    ueb_ziel: int
    """
    The identifier of the target point. Part of primary key.
    """

    @property
    def end_station_primary_key(self) -> Tuple[int, int, int]:
        """
        This key can be used to identify the start station by its primary key.
        """
        return self.basis_version, self.ueb_ziel_typ, self.ueb_ziel

    ueb_laenge: Optional[int]
    """
    Distance in meters.
    """

    @property
    def primary_key(self) -> Tuple[int | date | str, ...]:
        return self.basis_version, self.bereich_nr, self.onr_typ_nr, self.ort_nr, self.ueb_ziel_typ, self.ueb_ziel

    def to_route(
        self,
        scenario: Scenario,
        stations_by_basis_version_and_onr_typ_nr_and_ort_nr: Dict[Tuple[int, int, int], Station],
    ) -> Route:
        if self.ueb_laenge is None:
            raise ValueError("The ueb_laenge should not be None.")

        route = Route(
            scenario=scenario,
            name="Überläuferfahrt",
            departure_station=stations_by_basis_version_and_onr_typ_nr_and_ort_nr[self.start_station_primary_key],
            arrival_station=stations_by_basis_version_and_onr_typ_nr_and_ort_nr[self.end_station_primary_key],
            distance=self.ueb_laenge,
        )

        return route

    @classmethod
    def from_dict(cls, data: Dict[str, str | int | float | None]) -> "RecUeb":
        """
        Load the data from a dictionary.
        """

        assert isinstance(data["BASIS_VERSION"], int), "The `basis_version` should be an integer."
        assert isinstance(data["BEREICH_NR"], int), "The `bereich_nr` should be an integer."
        assert isinstance(data["ONR_TYP_NR"], int), "The `onr_typ_nr` should be an integer."
        assert isinstance(data["ORT_NR"], int), "The `ort_nr` should be an integer."
        assert isinstance(data["UEB_ZIEL_TYP"], int), "The `ueb_ziel_typ` should be an integer."
        assert isinstance(data["UEB_ZIEL"], int), "The `ueb_ziel` should be an integer."
        assert isinstance(data["UEB_LAENGE"], int), "The `ueb_laenge` should be an integer."

        return RecUeb(
            basis_version=data["BASIS_VERSION"],
            bereich_nr=data["BEREICH_NR"],
            onr_typ_nr=data["ONR_TYP_NR"],
            ort_nr=data["ORT_NR"],
            ueb_ziel_typ=data["UEB_ZIEL_TYP"],
            ueb_ziel=data["UEB_ZIEL"],
            ueb_laenge=data["UEB_LAENGE"],
        )


@dataclass
class RecLid(VdvBaseObject):
    """
    This is probably equivalent ot what we call a "Route" in efhlips-model
    """

    li_nr: int
    """
    ID of the route
    """

    str_li_var: str
    """
    Identifier for the variant of the route.
    """

    bereich_nr: int
    """
    Identifier of a branch. Hopefully I don't care about that. 
    """

    li_kuerzel: str
    """
    Corresponds to `Route.name_short`
    """

    lidname: Optional[str]
    """
    Correpsonds to `Route.name`
    """

    routen_art: RoutenArt
    """
    Vaguely corresponds to TripType in eflips-model
    """

    @property
    def primary_key(self) -> Tuple[int | date | str, ...]:
        return self.basis_version, self.li_nr, self.str_li_var

    @classmethod
    def from_dict(cls, data: Dict[str, str | int | float | None]) -> "RecLid":
        """
        Load the data from a dictionary.
        """

        assert isinstance(data["BASIS_VERSION"], int), "The `basis_version` should be an integer."
        assert isinstance(data["LI_NR"], int), "The `li_nr` should be an integer."
        assert isinstance(data["STR_LI_VAR"], str), "The `str_li_var` should be a string."
        assert isinstance(data["BEREICH_NR"], int), "The `bereich_nr` should be an integer."
        assert isinstance(data["LI_KUERZEL"], str), "The `li_kuerzel` should be a string."
        assert isinstance(data["LIDNAME"], str), "The `lidname` should be a string."
        assert isinstance(data["ROUTEN_ART"], int), "The `routen_art` should be an integer."

        return RecLid(
            basis_version=data["BASIS_VERSION"],
            li_nr=data["LI_NR"],
            str_li_var=data["STR_LI_VAR"],
            bereich_nr=data["BEREICH_NR"],
            li_kuerzel=data["LI_KUERZEL"],
            lidname=data["LIDNAME"],
            routen_art=RoutenArt(data["ROUTEN_ART"]),
        )

    def to_line(self, scenario: Scenario) -> Line:
        """
        Convert to a line. Note that since each route is a RecLid, converting all of them to lines will create duplicates.

        It is suggested to only convert the ones that are unique by li_nr
        :param scenario: The scenario to associate the line with
        :return: An instance of the Line class
        """
        return Line(
            scenario=scenario,
            name=self.li_kuerzel,
        )

    def to_route(
        self,
        scenario: Scenario,
        lines_by_basis_version_andli_nr: Dict[Tuple[int | date | str, ...], Line],
        rec_orts_by_basis_version_and_onr_typ_nr_and_ort_nr: Dict[Tuple[int, int, int], RecOrt],
        lid_verlaufs_by_basis_version_and_li_nr_and_str_li_var: Dict[Tuple[int, int, str], List[LidVerlauf]],
        stations_by_basis_version_and_onr_typ_nr_and_ort_nr: Dict[Tuple[int | date | str, ...], Station],
        rec_sel_by_basis_version_and_start_type_and_start_nr_and_end_type_and_end_nr: Dict[
            Tuple[int, int, int, int, int], RecSel
        ],
    ) -> Tuple[Route, List[RecSel]]:
        """
        Convert to a route.

        :param scenario: The scenario to associate the route with
        :return: An instance of the Route class
        """
        logger = logging.getLogger(__name__)

        route = Route(
            scenario=scenario,
            line=lines_by_basis_version_andli_nr[(self.basis_version, self.li_nr)],
            name=self.lidname,
        )

        assoc_route_stations: List[AssocRouteStation] = []

        this_routes_lid_verlaufs = lid_verlaufs_by_basis_version_and_li_nr_and_str_li_var[
            (self.basis_version, self.li_nr, self.str_li_var)
        ]
        elapsed_distance = 0
        rec_sels = []
        for i in range(len(this_routes_lid_verlaufs)):
            this_lid_verlauf = this_routes_lid_verlaufs[i]

            this_rec_ort = rec_orts_by_basis_version_and_onr_typ_nr_and_ort_nr[this_lid_verlauf.position_key]
            if this_rec_ort.latitude is None or this_rec_ort.longitude is None:
                logger.debug(f"Encountered a station without coordinates: {this_rec_ort}")
                location = None
            else:
                location = f"POINT({this_rec_ort.longitude} {this_rec_ort.latitude} {this_rec_ort.altitude})"

            this_station = stations_by_basis_version_and_onr_typ_nr_and_ort_nr[this_lid_verlauf.position_key]

            assoc = AssocRouteStation(
                scenario=scenario,
                route=route,
                station=this_station,
                location=location,
                elapsed_distance=elapsed_distance,
            )
            assoc_route_stations.append(assoc)

            if i == 0:
                route.departure_station = this_station

            if i < len(this_routes_lid_verlaufs) - 1:
                this_rec_sel = rec_sel_by_basis_version_and_start_type_and_start_nr_and_end_type_and_end_nr[
                    (
                        self.basis_version,
                        this_lid_verlauf.onr_typ_nr,
                        this_lid_verlauf.ort_nr,
                        this_routes_lid_verlaufs[i + 1].onr_typ_nr,
                        this_routes_lid_verlaufs[i + 1].ort_nr,
                    )
                ]
                rec_sels.append(this_rec_sel)
                if this_rec_sel.sel_laenge is None or this_rec_sel.sel_laenge == 0:
                    logger.debug(f"Encountered a segment without length: {this_rec_sel.primary_key}")
                    # put a default value
                    elapsed_distance += 1
                else:
                    elapsed_distance += this_rec_sel.sel_laenge
        route.arrival_station = this_station
        route.distance = elapsed_distance
        route.assoc_route_stations = assoc_route_stations

        return route, rec_sels


@dataclass(kw_only=True)
class RecUmlauf(VdvBaseObject):
    """
    The RecUmlauf corresponds to a `Rotation`in the eflips-model world. As such, we mainly care about it being a foreign
     key in RecFrt, but also it has a name.
    """

    tagesart_nr: int
    """
    The description of the type of day this rotation is valid at. Used to construct the valid rotations for a given day.
    """

    um_uid: int
    """
    Unique identifier of this rotation. Turns into "Rotation.name"
    """

    anf_ort: int
    """
    ID number of the start place. Possibly, we can use it to verify the rotation when building it, since 
    it should be the same as the start of the first trip's route.
    """

    anf_onr_typ: int
    """
    Type ID if the  start place.
    """

    @property
    def start_station_primary_key(self) -> Tuple[int, int, int]:
        """
        This key can be used to identify the start station by its primary key.
        """
        return self.basis_version, self.anf_onr_typ, self.anf_ort if self.anf_ort is not None else None

    end_ort: int
    """
    ID number of the end place. Possibly, we can use it to verify the rotation when building it, since 
    it should be the same as the start of the first trip's route.
    """

    end_onr_typ: int
    """
    Type ID if the end place.
    """

    @property
    def end_station_primary_key(self) -> Tuple[int, int, int]:
        """
        This key can be used to identify the end station by its primary key.
        """
        return self.basis_version, self.end_onr_typ, self.end_ort if self.end_ort is not None else None

    fzg_typ_nr: Optional[int]
    """
    Identifier of the vehicle type to take on theis rotation
    """

    @property
    def primary_key(self) -> Tuple[int | date | str, ...]:
        return self.basis_version, self.tagesart_nr, self.um_uid

    @classmethod
    def from_dict(cls, data: Dict[str, str | int | float | None]) -> "RecUmlauf":
        """
        Load the data from a dictionary.
        """

        assert isinstance(data["BASIS_VERSION"], int), "The `basis_version` should be an integer."
        assert isinstance(data["TAGESART_NR"], int), "The `tagesart_nr` should be an integer."
        assert isinstance(data["UM_UID"], int), "The `um_uid` should be an integer."
        assert isinstance(data["ANF_ORT"], int), "The `anf_ort` should be an integer."
        assert isinstance(data["ANF_ONR_TYP"], int), "The `anf_onr_typ` should be an integer."
        assert isinstance(data["END_ORT"], int), "The `end_ort` should be an integer."
        assert isinstance(data["END_ONR_TYP"], int), "The `end_onr_typ` should be an integer."

        return RecUmlauf(
            basis_version=data["BASIS_VERSION"],
            tagesart_nr=data["TAGESART_NR"],
            um_uid=data["UM_UID"],
            anf_ort=data["ANF_ORT"],
            anf_onr_typ=data["ANF_ONR_TYP"],
            end_ort=data["END_ORT"],
            end_onr_typ=data["END_ONR_TYP"],
            fzg_typ_nr=data["FZG_TYP_NR"]
            if "FZG_TYP_NR" in data.keys() and isinstance(data["FZG_TYP_NR"], int) and data["FZG_TYP_NR"] != 0
            else None,
        )

    def to_rotation(
        self,
        scenario: Scenario,
        db_vehicle_types_by_vdv_pk: Dict[Tuple[int | date | str, ...], VehicleType],
        allow_opportunity_charging: bool = False,
        dummy_vehicle_type: Optional[VehicleType] = None,
    ) -> Rotation:
        """
        Converts to a database rotation object.
        :param db_vehicle_types_by_vdv_pk: A dictionary of already-imported vehicle type objects by their VDV-style
        primary key, which is defined as BASIS_VERSION + FZG_TYPE_NR
        :param allow_opportunity_charging: Whether the vehicle is allowed to charge opportunistically.
        :param dummy_vehicle_type: A vehicle type to use if the vehicle type is not found in the dictionary.
        :return:
        """

        if self.fzg_typ_nr is None:
            if dummy_vehicle_type is None:
                raise ValueError("The vehicle type is not set and no dummy vehicle type is provided.")
            vehicle_type = dummy_vehicle_type
        else:
            vehicle_type = db_vehicle_types_by_vdv_pk[(self.basis_version, self.fzg_typ_nr)]

        return Rotation(
            scenario=scenario,
            name=self.um_uid,
            vehicle_type=vehicle_type,
            allow_opportunity_charging=allow_opportunity_charging,
        )


@dataclass(kw_only=True)
class MengeFzgTyp(VdvBaseObject):
    """
    This seems to be reasonably close to a vehicle type in the eflips-model world.
    """

    fzg_typ_nr: int
    """
    The unique identifier of the vehicle type.
    """

    fzg_typ_text: Optional[str]
    """
    Equivalent to `VehicleType.name` in the eflips-model world.
    """

    str_fzg_typ: Optional[str]
    """
    Equivalent to `VehicleType.name_short` in the eflips-model world.
    """

    fzg_laenge: Optional[float]
    """
    Length of the vehicle in meters.
    """

    fzg_hoehe: Optional[float]
    """
    Height of the vehicle in meters.
    """

    fzg_breite: Optional[float]
    """
    Width of the vehicle in meters.
    """

    fzg_gewicht: Optional[float]
    """
    The weight of the vehicle in kg.
    """

    verbrauch: Optional[float]
    """
    The energy consumption of the engine in kWh per km.
    """

    @property
    def primary_key(self) -> Tuple[int | date | str, ...]:
        return self.basis_version, self.fzg_typ_nr

    @classmethod
    def from_dict(cls, data: Dict[str, str | int | float | None]) -> "MengeFzgTyp":
        """
        Load the data from a dictionary.
        """

        if "FZG_TYP_TEXT" in data:
            assert isinstance(data["FZG_TYP_TEXT"], str), "The `FZG_TYP_TEXT` should be a string."
            fzg_typ_text = data["FZG_TYP_TEXT"]
        else:
            fzg_typ_text = None

        if "STR_FZG_TYP" in data:
            assert isinstance(data["STR_FZG_TYP"], str), "The `STR_FZG_TYP` should be a string."
            str_fzg_typ = data["STR_FZG_TYP"]
        else:
            str_fzg_typ = None

        if "FZG_LAENGE" in data:
            assert isinstance(data["FZG_LAENGE"], int), "The `FZG_LAENGE` should be an integer."
            fzg_laenge = data["FZG_LAENGE"]
        else:
            fzg_laenge = None
        if "FZG_TYP_HOEHE" in data:
            assert isinstance(data["FZG_TYP_HOEHE"], int), "The `FZG_TYP_HOEHE` should be an integer."
            fzg_hoehe = data["FZG_TYP_HOEHE"] / 100
        else:
            fzg_hoehe = None
        if "FZG_TYP_BREITE" in data:
            assert isinstance(data["FZG_TYP_BREITE"], int), "The `FZG_TYP_BREITE` should be an integer."
            fzg_breite = data["FZG_TYP_BREITE"] / 100
        else:
            fzg_breite = None
        if "FZG_TYP_GEWICHT" in data:
            assert isinstance(data["FZG_TYP_GEWICHT"], int), "The `FZG_TYP_GEWICHT` should be an integer."
            fzg_gewicht = data["FZG_TYP_GEWICHT"]
        else:
            fzg_gewicht = None

        # zero values are also invalid here
        if (
            fzg_laenge == 0
            or fzg_breite == 0
            or fzg_hoehe == 0
            or fzg_laenge is None
            or fzg_breite is None
            or fzg_hoehe is None
        ):
            # In our code, they either all need to be set or none of them.
            fzg_laenge = None
            fzg_breite = None
            fzg_hoehe = None
        if fzg_gewicht == 0:
            fzg_gewicht = None

        if "VERBRAUCH_DISTANZ" in data:
            assert isinstance(data["VERBRAUCH_DISTANZ"], int), "The `VERBRAUCH_DISTANZ` should be an integer."
            verbrauch_distanz = data["VERBRAUCH_DISTANZ"] / 1000  # Wh per km to kWh per km
        else:
            verbrauch_distanz = None

        if "VERBRAUCH_ZEIT" in data:
            assert isinstance(data["VERBRAUCH_ZEIT"], int), "The `VERBRAUCH_ZEIT` should be an integer."
            verbrauch_zeit = data["VERBRAUCH_ZEIT"] / 1000  # W to kW
        else:
            verbrauch_zeit = None

        if verbrauch_distanz is not None and verbrauch_zeit is not None:
            # We cannot do a distance and time consumption at the same time. For now, assume an average speed of 20 km_h
            AVERAGE_SPEED = 20
            time_per_kilometer = 1 / AVERAGE_SPEED  # hours
            verbrauch = verbrauch_distanz + verbrauch_zeit * time_per_kilometer
        else:
            verbrauch = None

        assert isinstance(data["BASIS_VERSION"], int), "The `basis_version` should be an integer."
        assert isinstance(data["FZG_TYP_NR"], int), "The `fzg_typ_nr` should be an integer."
        assert isinstance(data["FZG_TYP_TEXT"], str), "The `fzg_typ_text` should be a string."
        assert isinstance(data["STR_FZG_TYP"], str), "The `str_fzg_typ` should be a string."

        return MengeFzgTyp(
            basis_version=data["BASIS_VERSION"],
            fzg_typ_nr=data["FZG_TYP_NR"],
            fzg_typ_text=fzg_typ_text,
            str_fzg_typ=str_fzg_typ,
            fzg_laenge=fzg_laenge,
            fzg_hoehe=fzg_hoehe,
            fzg_breite=fzg_breite,
            fzg_gewicht=fzg_gewicht,
            verbrauch=verbrauch,
        )

    def to_vehicle_type(
        self,
        scenario: Scenario,
        battery_capacity: float = 1000,
        charging_curve: List[List[float]] = [[0, 1], [150, 150]],
        opportunity_charging_capable: bool = False,
    ) -> VehicleType:
        """
        Transform the object to a VehicleType object.

        :param scenario: an eflips-model scenario
        :param battery_capacity: THe battery capacity in kWh. Defaults to (unrealistically high) 1000

        :return: An eflips-model VehicleType object.
        """
        return VehicleType(
            scenario=scenario,
            name=self.fzg_typ_text,
            name_short=self.str_fzg_typ,
            length=self.fzg_laenge,
            height=self.fzg_hoehe,
            width=self.fzg_breite,
            consumption=self.verbrauch,
            battery_capacity=battery_capacity,
            charging_curve=charging_curve,
            empty_mass=self.fzg_gewicht,
            opportunity_charging_capable=opportunity_charging_capable,
        )
