// Copyright (C) 2024 Philipp Hofer
//
// Licensed under the EUPL, Version 1.2 or - as soon they will be approved by
// the European Commission - subsequent versions of the EUPL (the "Licence").
// You may not use this work except in compliance with the Licence.
//
// You should have received a copy of the European Union Public License along
// with this program.  If not, you may obtain a copy of the Licence at:
// <https://joinup.ec.europa.eu/software/page/eupl>
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the Licence is distributed on an "AS IS" basis,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the Licence for the specific language governing permissions and
// limitations under the Licence.

use std::collections::HashMap;

use serde::{Deserialize, Deserializer};

fn deserialize_string_to_usize<'de, D>(deserializer: D) -> Result<usize, D::Error>
where
    D: serde::Deserializer<'de>,
{
    let s = String::deserialize(deserializer)?;
    s.parse().map_err(serde::de::Error::custom)
}

#[derive(Deserialize)]
#[serde(rename_all = "PascalCase")]
#[allow(dead_code)]
pub(crate) struct OgdSearchResult {
    ogd_document_results: OgdDocumentResults,
}

impl OgdSearchResult {
    pub(crate) fn has_next_page(&self) -> bool {
        let hits = &self.ogd_document_results.hits;
        let curr_page_number = hits.page_number;
        let page_size = hits.page_size;
        let elements = hits.text;

        let parsed_so_far = curr_page_number * page_size;

        elements > parsed_so_far
    }

    pub(crate) fn get_par(&self) -> Vec<String> {
        let mut ret = Vec::new();
        for doc_ref in &self.ogd_document_results.ogd_document_reference {
            for con_refs in &doc_ref.data.document_list.content_reference {
                for urls in &con_refs.urls.content_url {
                    if urls.data_type == "Xml" {
                        ret.push(urls.url.clone());
                    }
                }
            }
        }
        ret
    }
}

#[derive(Deserialize)]
#[serde(rename_all = "PascalCase")]
#[allow(dead_code)]
pub(crate) struct OgdDocumentResults {
    hits: Hits,
    ogd_document_reference: Vec<OgdDocumentReference>,
}

#[derive(Deserialize)]
#[allow(dead_code)]
pub(crate) struct Hits {
    #[serde(
        rename = "@pageNumber",
        deserialize_with = "deserialize_string_to_usize"
    )]
    page_number: usize,
    #[serde(rename = "@pageSize", deserialize_with = "deserialize_string_to_usize")]
    page_size: usize,
    #[serde(rename = "#text", deserialize_with = "deserialize_string_to_usize")]
    text: usize,
}

#[derive(Deserialize)]
#[allow(dead_code)]
#[serde(rename_all = "PascalCase")]
pub(crate) struct OgdDocumentReference {
    data: Data,
}

#[derive(Deserialize)]
#[allow(dead_code)]
pub(crate) struct Data {
    #[serde(rename = "Metadaten")]
    metadata: Metadata,
    #[serde(rename = "Dokumentliste")]
    document_list: DocumentList,
}

#[derive(Deserialize)]
#[allow(dead_code)]
pub(crate) struct Metadata {
    #[serde(rename = "Technisch")]
    technical: TechnicalMetadata,

    #[serde(rename = "Allgemein")]
    general: GeneralMetadata,

    #[serde(flatten)]
    law_type: LawType,
}

#[derive(Deserialize)]
enum LawType {
    #[serde(rename = "Bundesrecht")]
    Federal(FedMetadata),
    #[serde(rename = "Landesrecht")]
    State(LocMetadata),
}

#[derive(Deserialize)]
#[allow(dead_code)]
#[serde(rename_all = "PascalCase")]
pub(crate) struct TechnicalMetadata {
    #[serde(rename = "ID")]
    id: String,

    #[serde(rename = "Applikation")]
    application: String,

    organ: String,

    import_timestamp: ImportTimestamp,
}

#[derive(Deserialize)]
#[allow(dead_code)]
pub(crate) struct ImportTimestamp {
    #[serde(rename = "@xsi:nil")]
    xsi_nil: String, //TODO: switch to bool

    #[serde(rename = "@xmlns:xsi")]
    xmlns_xsi: String,
}

#[derive(Deserialize)]
#[allow(dead_code)]
pub(crate) struct GeneralMetadata {
    #[serde(rename = "Geaendert")]
    changed: String, //TODO: switch to YYYY-MM-DD string

    #[serde(rename = "DokumentUrl")]
    document_url: String,
}

// TODO: check if more data is given to me (nom nom nom)
#[derive(Deserialize)]
#[allow(dead_code)]
#[serde(rename_all = "PascalCase")]
pub(crate) struct LocMetadata {
    #[serde(rename = "Kurztitel")]
    short_title: String,

    #[serde(rename = "Titel")]
    title: Option<String>,
}

#[derive(Deserialize)]
#[allow(dead_code)]
#[serde(rename_all = "PascalCase")]
pub(crate) struct FedMetadata {
    #[serde(rename = "Kurztitel")]
    short_title: String,

    #[serde(rename = "Titel")]
    title: Option<String>,

    eli: String,

    br_kons: BrKons,
}

#[derive(Deserialize)]
#[allow(dead_code)]
#[serde(rename_all = "PascalCase")]
pub(crate) struct BrKons {
    kundmachungsorgan: String,
    typ: String,
    #[serde(rename = "Dokumenttyp")]
    documenttype: String,
    artikel_paragraph_anlage: String,
    paragraphnummer: Option<String>,
    stammnorm_publikationsorgan: String,
    stammnorm_bgblnummer: String,
    inkrafttretensdatum: String, //TODO: switch to date
    #[serde(rename = "Indizes")]
    indices: HashMap<String, String>, //e.g. "item": "22/04 Sonstiges Zivilprozess, Außerstreitiges Verfahren"
    #[serde(rename = "Aenderung")]
    change: String,
    anmerkung: Option<String>,
    schlagworte: Option<String>,
    gesetzesnummer: String,
    alte_dokumentnummer: Option<String>,
    #[serde(rename = "GesamteRechtsvorschriftUrl")]
    full_url: String,
}

#[derive(Deserialize)]
#[allow(dead_code)]
#[serde(rename_all = "PascalCase")]
pub(crate) struct DocumentList {
    #[serde(deserialize_with = "deserialize_content_reference")]
    content_reference: Vec<ContentReference>,
}

fn deserialize_content_reference<'de, D>(deserializer: D) -> Result<Vec<ContentReference>, D::Error>
where
    D: Deserializer<'de>,
{
    #[derive(Deserialize)]
    #[serde(untagged)]
    enum ContentReferenceField {
        Single(ContentReference),
        Multiple(Vec<ContentReference>),
    }

    let field = ContentReferenceField::deserialize(deserializer)?;
    Ok(match field {
        ContentReferenceField::Single(item) => vec![item],
        ContentReferenceField::Multiple(items) => items,
    })
}

#[derive(Deserialize)]
#[allow(dead_code)]
#[serde(rename_all = "PascalCase")]
pub(crate) struct ContentReference {
    content_type: String,
    name: String,
    urls: ContentUrl,
}

#[derive(Deserialize)]
#[allow(dead_code)]
#[serde(rename_all = "PascalCase")]
pub(crate) struct ContentUrl {
    #[serde(deserialize_with = "deserialize_content_url")]
    content_url: Vec<ContentUrlItem>,
}

fn deserialize_content_url<'de, D>(deserializer: D) -> Result<Vec<ContentUrlItem>, D::Error>
where
    D: Deserializer<'de>,
{
    #[derive(Deserialize)]
    #[serde(untagged)]
    enum ContentUrlField {
        Single(ContentUrlItem),
        Multiple(Vec<ContentUrlItem>),
    }

    let field = ContentUrlField::deserialize(deserializer)?;
    Ok(match field {
        ContentUrlField::Single(item) => vec![item],
        ContentUrlField::Multiple(items) => items,
    })
}

#[derive(Deserialize)]
#[allow(dead_code)]
#[serde(rename_all = "PascalCase")]
pub(crate) struct ContentUrlItem {
    data_type: String,
    url: String,
}
