// SPDX-FileCopyrightText: Politik im Blick developers
// SPDX-FileCopyrightText: Wolfgang Silbermayr <wolfgang@silbermayr.at>
//
// SPDX-License-Identifier: AGPL-3.0-or-later OR EUPL-1.2

use std::num::NonZero;

use crate::Url;

#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct DataListLinks {
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub first: Option<Url>,

    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub prev: Option<Url>,

    #[serde(rename = "self", default, skip_serializing_if = "Option::is_none")]
    pub self_: Option<Url>,

    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub next: Option<Url>,

    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub last: Option<Url>,

    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub web: Option<Url>,

    #[serde(default, flatten)]
    pub extensions: serde_json::Map<String, serde_json::Value>,
}

impl DataListLinks {
    pub fn for_paging(
        oparl_url: Url,
        web_url: Option<Url>,
        page: usize,
        page_size: NonZero<usize>,
        count: Option<usize>,
    ) -> Self {
        let first = Some(oparl_url.clone());
        let prev = match page {
            0 => None,
            _ => Some(oparl_url.clone().for_page(page - 1)),
        };
        let self_ = Some(oparl_url.clone().for_page(page));
        let next = match count {
            None => Some(oparl_url.clone().for_page(page + 1)),
            Some(count) if count > (page + 1) * usize::from(page_size) => {
                Some(oparl_url.clone().for_page(page + 1))
            }
            Some(_) => None,
        };
        let last = count.map(|count| oparl_url.clone().for_page(count / page_size));
        let web = web_url.map(|web_url| web_url.for_page(page));

        Self {
            first,
            prev,
            self_,
            next,
            last,
            web,
            extensions: serde_json::Map::new(),
        }
    }
}

trait BuildUrl {
    fn for_page(self, page: usize) -> Self;
}

impl BuildUrl for Url {
    fn for_page(mut self, page: usize) -> Self {
        match page {
            0 => {
                self.set_query(None);
                self
            }
            _ => {
                self.set_query(Some(&format!("page={page}")));
                self
            }
        }
    }
}

#[cfg(test)]
mod serde_tests {
    use pretty_assertions::assert_eq;
    use serde_json::json;

    use super::DataListLinks;

    fn example_data_list_links() -> DataListLinks {
        DataListLinks {
            first: Some(
                "https://oparl.example.org/organization/"
                    .parse()
                    .expect("value must be parseable as url"),
            ),
            prev: Some(
                "https://oparl.example.org/organization/?page=2"
                    .parse()
                    .expect("value must be parseable as url"),
            ),
            self_: Some(
                "https://oparl.example.org/organization/?page=3"
                    .parse()
                    .expect("value must be parseable as url"),
            ),
            next: Some(
                "https://oparl.example.org/organization/?page=4"
                    .parse()
                    .expect("value must be parseable as url"),
            ),
            last: Some(
                "https://oparl.example.org/organization/?page=500"
                    .parse()
                    .expect("value must be parseable as url"),
            ),
            web: Some(
                "https://web.example.org/organization/?page=500"
                    .parse()
                    .expect("value must be parseable as url"),
            ),
            extensions: serde_json::Map::new(),
        }
    }

    fn example_data_list_links_json() -> serde_json::Value {
        json!({
            "first": "https://oparl.example.org/organization/",
            "prev": "https://oparl.example.org/organization/?page=2",
            "self": "https://oparl.example.org/organization/?page=3",
            "next": "https://oparl.example.org/organization/?page=4",
            "last": "https://oparl.example.org/organization/?page=500",
            "web": "https://web.example.org/organization/?page=500"
        })
    }

    #[test]
    fn serialize() {
        assert_eq!(
            json!(example_data_list_links()),
            example_data_list_links_json()
        );
    }

    #[test]
    fn deserialize_good() {
        let deserialized: DataListLinks = serde_json::from_value(example_data_list_links_json())
            .expect("value must be deserializable as DataListLinks");
        assert_eq!(deserialized, example_data_list_links());
    }

    #[test]
    fn serialize_empty() {
        assert_eq!(
            json!(DataListLinks {
                first: None,
                prev: None,
                self_: None,
                next: None,
                last: None,
                web: None,
                extensions: serde_json::Map::new()
            }),
            json!({}),
        );
    }

    #[test]
    fn deserialize_empty() {
        let deserialized: DataListLinks = serde_json::from_value(json!({}))
            .expect("value must be deserializable as DataListLinks");
        assert_eq!(
            deserialized,
            DataListLinks {
                first: None,
                prev: None,
                self_: None,
                next: None,
                last: None,
                web: None,
                extensions: serde_json::Map::new()
            }
        );
    }

    #[test]
    fn deserialize_bad() {
        assert!(serde_json::from_value::<DataListLinks>(json!("xyzabcd")).is_err());
        assert!(serde_json::from_value::<DataListLinks>(json!(true)).is_err());
        assert!(serde_json::from_value::<DataListLinks>(json!(123)).is_err());
    }
}
