// 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 crate::{
    AgendaItem, DateTime, File, Keyword, Location, MeetingUrl, Name, OrganizationUrl, Url,
    namespace::MeetingNamespaceUrl,
};

#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Meeting {
    pub id: MeetingUrl,

    #[serde(rename = "type")]
    pub namespace: MeetingNamespaceUrl,

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

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

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

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

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

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

    #[serde(default, skip_serializing_if = "Vec::is_empty")]
    pub organization: Vec<OrganizationUrl>,

    #[serde(default, skip_serializing_if = "Vec::is_empty")]
    pub participant: Vec<Url>,

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

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

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

    #[serde(default, skip_serializing_if = "Vec::is_empty")]
    pub auxiliary_file: Vec<File>,

    #[serde(default, skip_serializing_if = "Vec::is_empty")]
    pub agenda_item: Vec<AgendaItem>,

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

    #[serde(default, skip_serializing_if = "Vec::is_empty")]
    pub keyword: Vec<Keyword>,

    pub created: DateTime,

    pub modified: DateTime,

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

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

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

#[cfg(test)]
mod serde_tests {
    use pretty_assertions::assert_eq;
    use serde_json::json;
    use time::macros::{date, datetime};

    use super::Meeting;
    use crate::{
        AgendaItem, File, Location, Sha1Sum,
        namespace::{
            AgendaItemNamespaceUrl, FileNamespaceUrl, LocationNamespaceUrl, MeetingNamespaceUrl,
        },
    };

    fn example_meeting() -> Meeting {
        let geojson_feature = {
            let mut f =
                geojson::Feature::from(geojson::Geometry::new(geojson::GeometryValue::Point {
                    coordinates: [50.1234, 10.4321].into(),
                }));
            f.set_property("name", "Rathausplatz");
            f
        };

        Meeting {
            id: "https://oparl.example.org/meeting/281"
                .parse()
                .expect("value must be parseable as id"),
            namespace: MeetingNamespaceUrl::Identifier,
            name: Some("4. Sitzung des Finanzausschusses".into()),
            cancelled: None,
            meeting_state: None,
            start: Some(datetime!(2013 - 01 - 04 08:00:00 +01:00).into()),
            end: Some(datetime!(2013 - 01 - 04 12:00:00 +01:00).into()),
            location: Some(Location {
                id: "https://oparl.example.org/location/0"
                    .parse()
                    .expect("value must be parseable as id"),
                namespace: LocationNamespaceUrl::Identifier,
                description: Some(
                    "Rathaus der Beispielstadt, Ratshausplatz 1, 12345 Beispielstadt".to_string(),
                ),
                geojson: Some(geojson_feature.into()),
                street_address: None,
                room: None,
                postal_code: None,
                sub_locality: None,
                locality: None,
                bodies: vec![],
                organizations: vec![],
                persons: vec![],
                meetings: vec![],
                papers: vec![],
                license: None,
                keyword: vec![],
                created: datetime!(2012-01-06 12:01:00 +01:00).into(),
                modified: datetime!(2012-01-08 14:05:27 +01:00).into(),
                web: None,
                deleted: None,
                extensions: serde_json::Map::new(),
            }),
            organization: vec![
                "https://oparl.example.org/organization/34"
                    .parse()
                    .expect("value must be parseable as url"),
            ],
            participant: vec![],
            invitation: Some(File {
                id: "https://oparl.example.org/files/57739"
                    .parse()
                    .expect("value must be parseable as id"),
                namespace: FileNamespaceUrl::Identifier,
                file_name: Some("einladung.pdf".into()),
                name: Some("Einladung".into()),
                mime_type: Some("application/pdf".into()),
                date: Some(date!(2012 - 01 - 08).into()),
                size: Some(82930),
                sha1_checksum: Some(Sha1Sum::from([
                    0xda, 0x39, 0xa3, 0xee, 0x5e, 0x6b, 0x4b, 0x0d, 0x32, 0x55, 0xbf, 0xef, 0x95,
                    0x60, 0x18, 0x90, 0xaf, 0xd8, 0x07, 0x09,
                ])),
                sha512_checksum: None,
                text: None,
                access_url: "https://oparl.example.org/files/57739.pdf"
                    .parse()
                    .expect("value must be parseable as url"),
                download_url: Some(
                    "https://oparl.example.org/files/download/57739.pdf"
                        .parse()
                        .expect("value must be parseable as url"),
                ),
                external_service_url: None,
                master_file: None,
                derivative_file: vec![],
                file_license: None,
                meeting: vec![],
                agenda_item: vec![],
                paper: vec![],
                license: None,
                keyword: vec![],
                created: datetime!(2012-01-06 12:01:00 +01:00).into(),
                modified: datetime!(2012-01-08 14:05:27 +01:00).into(),
                web: None,
                deleted: None,
                extensions: serde_json::Map::new(),
            }),
            results_protocol: Some(File {
                id: "https://oparl.example.org/files/57739"
                    .parse()
                    .expect("value must be parseable as id"),
                namespace: FileNamespaceUrl::Identifier,
                file_name: Some("protokoll.pdf".into()),
                name: Some("Protokoll".into()),
                mime_type: Some("application/pdf".into()),
                date: Some(date!(2012 - 01 - 08).into()),
                size: Some(82930),
                sha1_checksum: Some(Sha1Sum::from([
                    0xda, 0x39, 0xa3, 0xee, 0x5e, 0x6b, 0x4b, 0x0d, 0x32, 0x55, 0xbf, 0xef, 0x95,
                    0x60, 0x18, 0x90, 0xaf, 0xd8, 0x07, 0x09,
                ])),
                sha512_checksum: None,
                text: None,
                access_url: "https://oparl.example.org/files/57739.pdf"
                    .parse()
                    .expect("value must be parseable as url"),
                download_url: Some(
                    "https://oparl.example.org/files/download/57739.pdf"
                        .parse()
                        .expect("value must be parseable as url"),
                ),
                external_service_url: None,
                master_file: None,
                derivative_file: vec![],
                file_license: None,
                meeting: vec![],
                agenda_item: vec![],
                paper: vec![],
                license: None,
                keyword: vec![],
                created: datetime!(2012-01-06 12:01:00 +01:00).into(),
                modified: datetime!(2012-01-08 14:05:27 +01:00).into(),
                web: None,
                deleted: None,
                extensions: serde_json::Map::new(),
            }),
            verbatim_protocol: Some(File {
                id: "https://oparl.example.org/files/57739"
                    .parse()
                    .expect("value must be parseable as id"),
                namespace: FileNamespaceUrl::Identifier,
                file_name: Some("wortprotokoll.pdf".into()),
                name: Some("Wortprotokoll".into()),
                mime_type: Some("application/pdf".into()),
                date: Some(date!(2012 - 01 - 08).into()),
                size: Some(82930),
                sha1_checksum: Some(Sha1Sum::from([
                    0xda, 0x39, 0xa3, 0xee, 0x5e, 0x6b, 0x4b, 0x0d, 0x32, 0x55, 0xbf, 0xef, 0x95,
                    0x60, 0x18, 0x90, 0xaf, 0xd8, 0x07, 0x09,
                ])),
                sha512_checksum: None,
                text: None,
                access_url: "https://oparl.example.org/files/57739.pdf"
                    .parse()
                    .expect("value must be parseable as url"),
                download_url: Some(
                    "https://oparl.example.org/files/download/57739.pdf"
                        .parse()
                        .expect("value must be parseable as url"),
                ),
                external_service_url: None,
                master_file: None,
                derivative_file: vec![],
                file_license: None,
                meeting: vec![],
                agenda_item: vec![],
                paper: vec![],
                license: None,
                keyword: vec![],
                created: datetime!(2012-01-08 14:05:27 +01:00).into(),
                modified: datetime!(2012-01-08 14:05:27 +01:00).into(),
                web: None,
                deleted: None,
                extensions: serde_json::Map::new(),
            }),
            auxiliary_file: vec![File {
                id: "https://oparl.example.org/files/57739"
                    .parse()
                    .expect("value must be parseable as id"),
                namespace: FileNamespaceUrl::Identifier,
                file_name: Some("nachtrag-TO.pdf".into()),
                name: Some("Nachtrags-Tagesordnung".into()),
                mime_type: Some("application/pdf".into()),
                date: Some(date!(2012 - 01 - 08).into()),
                size: Some(82930),
                sha1_checksum: Some(Sha1Sum::from([
                    0xda, 0x39, 0xa3, 0xee, 0x5e, 0x6b, 0x4b, 0x0d, 0x32, 0x55, 0xbf, 0xef, 0x95,
                    0x60, 0x18, 0x90, 0xaf, 0xd8, 0x07, 0x09,
                ])),
                sha512_checksum: None,
                text: None,
                access_url: "https://oparl.example.org/files/57739.pdf"
                    .parse()
                    .expect("value must be parseable as url"),
                download_url: Some(
                    "https://oparl.example.org/files/download/57739.pdf"
                        .parse()
                        .expect("value must be parseable as url"),
                ),
                external_service_url: None,
                master_file: None,
                derivative_file: vec![],
                file_license: None,
                meeting: vec![],
                agenda_item: vec![],
                paper: vec![],
                license: None,
                keyword: vec![],
                created: datetime!(2012-01-08 14:05:27 +01:00).into(),
                modified: datetime!(2012-01-08 14:05:27 +01:00).into(),
                web: None,
                deleted: None,
                extensions: serde_json::Map::new(),
            }],
            agenda_item: vec![AgendaItem {
                id: "https://oparl.example.org/agendaitem/3271"
                    .parse()
                    .expect("value must be parseable as id"),
                namespace: AgendaItemNamespaceUrl::Identifier,
                meeting: Some(
                    "https://oparl.example.org/meeting/281"
                        .parse()
                        .expect("value must be parseable as url"),
                ),
                number: Some("10.1".into()),
                order: 0,
                name: Some("Satzungsänderung für Ausschreibungen".into()),
                public: Some(true),
                consultation: Some(
                    "https://oparl.example.org/consultation/1034"
                        .parse()
                        .expect("value must be parseable as url"),
                ),
                result: Some("Geändert beschlossen".into()),
                resolution_text: Some("Der Beschluss weicht wie folgt vom Antrag ab: ...".into()),
                resolution_file: None,
                auxiliary_file: vec![],
                start: None,
                end: None,
                license: None,
                keyword: vec![],
                created: datetime!(2012-01-06 12:01:00 +01:00).into(),
                modified: datetime!(2012-01-08 14:05:27 +01:00).into(),
                web: None,
                deleted: None,
                extensions: serde_json::Map::new(),
            }],
            license: None,
            keyword: vec![],
            created: datetime!(2012-01-06 12:01:00 +01:00).into(),
            modified: datetime!(2012-01-08 14:05:27 +01:00).into(),
            web: None,
            deleted: None,
            extensions: serde_json::Map::new(),
        }
    }

    fn example_meeting_json() -> serde_json::Value {
        json!({
            "id": "https://oparl.example.org/meeting/281",
            "type": "https://schema.oparl.org/1.1/Meeting",
            "name": "4. Sitzung des Finanzausschusses",
            "start": "2013-01-04T08:00:00+01:00",
            "end": "2013-01-04T12:00:00+01:00",
            "location": {
                "id": "https://oparl.example.org/location/0",
                "type": "https://schema.oparl.org/1.1/Location",
                "description": "Rathaus der Beispielstadt, Ratshausplatz 1, 12345 Beispielstadt",
                "created": "2012-01-06T12:01:00+01:00",
                "modified": "2012-01-08T14:05:27+01:00",
                "geojson": {
                    "type": "Feature",
                    "geometry": {
                        "type": "Point",
                        "coordinates": [
                            50.1234,
                            10.4321
                        ]
                    },
                    "properties": {
                        "name": "Rathausplatz"
                    }
                }
            },
            "organization": [
                "https://oparl.example.org/organization/34"
            ],
            "invitation": {
                "id": "https://oparl.example.org/files/57739",
                "type": "https://schema.oparl.org/1.1/File",
                "name": "Einladung",
                "fileName": "einladung.pdf",
                "mimeType": "application/pdf",
                "date": "2012-01-08",
                "modified": "2012-01-08T14:05:27+01:00",
                "sha1Checksum": "da39a3ee5e6b4b0d3255bfef95601890afd80709",
                "size": 82930,
                "accessUrl": "https://oparl.example.org/files/57739.pdf",
                "downloadUrl": "https://oparl.example.org/files/download/57739.pdf",
                "created": "2012-01-06T12:01:00+01:00"
            },
            "resultsProtocol": {
                "id": "https://oparl.example.org/files/57739",
                "type": "https://schema.oparl.org/1.1/File",
                "name": "Protokoll",
                "fileName": "protokoll.pdf",
                "mimeType": "application/pdf",
                "date": "2012-01-08",
                "sha1Checksum": "da39a3ee5e6b4b0d3255bfef95601890afd80709",
                "size": 82930,
                "accessUrl": "https://oparl.example.org/files/57739.pdf",
                "downloadUrl": "https://oparl.example.org/files/download/57739.pdf",
                "modified": "2012-01-08T14:05:27+01:00",
                "created": "2012-01-06T12:01:00+01:00"
            },
            "verbatimProtocol": {
                "id": "https://oparl.example.org/files/57739",
                "type": "https://schema.oparl.org/1.1/File",
                "name": "Wortprotokoll",
                "fileName": "wortprotokoll.pdf",
                "mimeType": "application/pdf",
                "date": "2012-01-08",
                "sha1Checksum": "da39a3ee5e6b4b0d3255bfef95601890afd80709",
                "size": 82930,
                "accessUrl": "https://oparl.example.org/files/57739.pdf",
                "downloadUrl": "https://oparl.example.org/files/download/57739.pdf",
                "modified": "2012-01-08T14:05:27+01:00",
                "created": "2012-01-08T14:05:27+01:00"
            },
            "auxiliaryFile": [
                {
                    "id": "https://oparl.example.org/files/57739",
                    "type": "https://schema.oparl.org/1.1/File",
                    "name": "Nachtrags-Tagesordnung",
                    "fileName": "nachtrag-TO.pdf",
                    "mimeType": "application/pdf",
                    "date": "2012-01-08",
                    "sha1Checksum": "da39a3ee5e6b4b0d3255bfef95601890afd80709",
                    "size": 82930,
                    "accessUrl": "https://oparl.example.org/files/57739.pdf",
                    "downloadUrl": "https://oparl.example.org/files/download/57739.pdf",
                    "modified": "2012-01-08T14:05:27+01:00",
                    "created": "2012-01-08T14:05:27+01:00"
                }
            ],
            "agendaItem": [
                {
                    "id": "https://oparl.example.org/agendaitem/3271",
                    "type": "https://schema.oparl.org/1.1/AgendaItem",
                    "meeting": "https://oparl.example.org/meeting/281",
                    "number": "10.1",
                    "order": 0,
                    "name": "Satzungsänderung für Ausschreibungen",
                    "public": true,
                    "consultation": "https://oparl.example.org/consultation/1034",
                    "result": "Geändert beschlossen",
                    "resolutionText": "Der Beschluss weicht wie folgt vom Antrag ab: ...",
                    "created": "2012-01-06T12:01:00+01:00",
                    "modified": "2012-01-08T14:05:27+01:00"
                }
            ],
            "created": "2012-01-06T12:01:00+01:00",
            "modified": "2012-01-08T14:05:27+01:00"
        })
    }

    #[test]
    fn serialize() {
        assert_eq!(json!(example_meeting()), example_meeting_json());
    }

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

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