use std::borrow::Cow;

use chrono::DateTime;
use chrono::Duration;
use chrono::FixedOffset;
#[cfg(feature = "polylines")]
use geojson::FeatureCollection;
use serde::{Deserialize, Serialize};

/* Types */

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(untagged)]
pub enum Location {
    Address {
        address: String,
        latitude: f32,
        longitude: f32,
    },
    Point {
        #[serde(skip_serializing_if = "Option::is_none")]
        id: Option<String>,
        #[serde(skip_serializing_if = "Option::is_none")]
        name: Option<String>,
        #[serde(skip_serializing_if = "Option::is_none")]
        poi: Option<bool>,
        latitude: f32,
        longitude: f32,
    },
}

impl PartialEq for Location {
    fn eq(&self, other: &Self) -> bool {
        match (&self, other) {
            (Location::Address {
                address: a,
                ..
            }, Location::Address {
                address: b,
                ..
            }) => {
                a == b
            }
            (Location::Point {
                id: Some(a),
                ..
            }, Location::Point {
                id: Some(b),
                ..
            }) => {
                a == b
            }
            (_, _) => false,
        }
    }
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(tag = "type")]
#[serde(rename_all = "lowercase")]
pub enum Place {
    Stop(Stop),
    Location(Location),
}

/*#[derive(Debug, Clone, Serialize)]
pub struct Station {
    pub id: u64,
    pub name: String,
    pub coordinates: Coordinates,
    pub products: Products,
}*/

#[derive(Debug, Default, Clone, Serialize, Deserialize)]
pub struct Stop {
    pub id: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub name: Option<String>,
    pub location: Option<Location>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub products: Option<Vec<Product>>,
    //station: Option<Station>,
}

impl PartialEq for Stop {
    fn eq(&self, other: &Self) -> bool {
        self.id == other.id
    }
}

// None means true
// TODO: What to do with the new products for other providers?
#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct ProductsSelection {
    pub national_express: Option<bool>,
    pub national: Option<bool>,
    pub regional_exp: Option<bool>,
    pub regional: Option<bool>,
    pub suburban: Option<bool>,
    pub bus: Option<bool>,
    pub ferry: Option<bool>,
    pub subway: Option<bool>,
    pub tram: Option<bool>,
    pub taxi: Option<bool>,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct Product {
    pub id: Cow<'static, str>,
    pub mode: Mode,
    pub bitmasks: Cow<'static, [u16]>,
    pub name: Cow<'static, str>,
    pub short: Cow<'static, str>,
    // TODO: default?
}

#[derive(Serialize, Debug, Clone, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "lowercase")]
pub enum Mode {
    Train,
    Bus,
    Watercraft,
    Taxi,
    Walking,
    Gondola,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub enum LoyaltyCard {
    BahnCard25Class1,
    BahnCard25Class2,
    BahnCard50Class1,
    BahnCard50Class2,
    Vorteilscard,
    HalbtaxaboRailplus,
    Halbtaxabo,
    VoordeelurenaboRailplus,
    Voordeelurenabo,
    SHCard,
    Generalabonnement,
}

impl LoyaltyCard {
    /// See https://gist.github.com/juliuste/202bb04f450a79f8fa12a2ec3abcd72d
    pub fn from_id(value: u8) -> Option<Self> {
        match value {
            1 => Some(LoyaltyCard::BahnCard25Class1),
            2 => Some(LoyaltyCard::BahnCard25Class2),
            3 => Some(LoyaltyCard::BahnCard50Class1),
            4 => Some(LoyaltyCard::BahnCard50Class2),
            9 => Some(LoyaltyCard::Vorteilscard),
            10 => Some(LoyaltyCard::HalbtaxaboRailplus),
            11 => Some(LoyaltyCard::Halbtaxabo),
            12 => Some(LoyaltyCard::VoordeelurenaboRailplus),
            13 => Some(LoyaltyCard::Voordeelurenabo),
            14 => Some(LoyaltyCard::SHCard),
            15 => Some(LoyaltyCard::Generalabonnement),
            _ => None,
        }
    }

    pub fn to_id(self) -> u8 {
        match self {
            LoyaltyCard::BahnCard25Class1 => 1,
            LoyaltyCard::BahnCard25Class2 => 2,
            LoyaltyCard::BahnCard50Class1 => 3,
            LoyaltyCard::BahnCard50Class2 => 4,
            LoyaltyCard::Vorteilscard => 9,
            LoyaltyCard::HalbtaxaboRailplus => 10,
            LoyaltyCard::Halbtaxabo => 11,
            LoyaltyCard::VoordeelurenaboRailplus => 12,
            LoyaltyCard::Voordeelurenabo => 13,
            LoyaltyCard::SHCard => 14,
            LoyaltyCard::Generalabonnement => 15,
        }
    }
}

#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
pub enum TariffClass {
    First,
    Second,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "lowercase")]
pub enum Accessibility {
    r#None,
    Partial,
    Complete,
}

#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "kebab-case")]
pub enum LoadFactor {
    LowToMedium,
    High,
    VeryHigh,
    ExceptionallyHigh,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct Line {
    pub name: Option<String>,
    pub fahrt_nr: Option<String>,
    pub mode: Mode,
    pub product: Product,
    pub operator: Option<Operator>,
    pub product_name: Option<String>,
}

#[derive(Debug, Serialize, Clone, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct Frequency {
    #[serde(with = "crate::serialize::duration")]
    pub minimum: Option<Duration>,
    #[serde(with = "crate::serialize::duration")]
    pub maximum: Option<Duration>,
    pub iterations: Option<u64>,
}

#[derive(Debug, Serialize, Clone, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct Leg {
    pub origin: Place,
    pub destination: Place,
    pub departure: Option<DateTime<FixedOffset>>,
    pub planned_departure: Option<DateTime<FixedOffset>>,
    pub departure_delay: Option<i64>,
    pub arrival: Option<DateTime<FixedOffset>>,
    pub planned_arrival: Option<DateTime<FixedOffset>>,
    pub arrival_delay: Option<i64>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub reachable: Option<bool>,
    pub trip_id: Option<String>,
    pub line: Option<Line>,
    pub direction: Option<String>,
    //current_location,
    pub arrival_platform: Option<String>,
    pub planned_arrival_platform: Option<String>,
    pub departure_platform: Option<String>,
    pub planned_departure_platform: Option<String>,
    pub frequency: Option<Frequency>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub cancelled: Option<bool>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub stopovers: Option<Vec<Stopover>>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub load_factor: Option<LoadFactor>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub remarks: Option<Vec<Remark>>,
    #[cfg(feature = "polylines")]
    #[serde(skip_serializing_if = "Option::is_none")]
    pub polyline: Option<FeatureCollection>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub walking: Option<bool>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub transfer: Option<bool>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub distance: Option<u64>,
}

#[derive(Debug, Serialize, Clone, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct Stopover {
    pub stop: Place,
    pub departure: Option<DateTime<FixedOffset>>,
    pub planned_departure: Option<DateTime<FixedOffset>>,
    pub departure_delay: Option<i64>,
    pub arrival: Option<DateTime<FixedOffset>>,
    pub planned_arrival: Option<DateTime<FixedOffset>>,
    pub arrival_delay: Option<i64>,
    pub arrival_platform: Option<String>,
    pub planned_arrival_platform: Option<String>,
    pub departure_platform: Option<String>,
    pub planned_departure_platform: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub cancelled: Option<bool>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub remarks: Option<Vec<Remark>>,
}

#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
pub struct Price {
    pub amount: f64,
    pub currency: String,
}

#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct Journey {
    #[serde(skip_serializing_if = "Option::is_none")]
    pub refresh_token: Option<String>,
    pub legs: Vec<Leg>,
    pub price: Option<Price>,
    //last_updated
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct Operator {
    pub id: String,
    pub name: String,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "lowercase")]
pub enum RemarkType {
    Hint,
    Status,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct Remark {
    pub code: String,
    pub text: String,
    pub r#type: RemarkType,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub summary: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub trip_id: Option<String>,
}
