//! Deserialize responses from Tocco's REST API

use crate::FileName;
use chrono::DateTime;
use serde::de::Error;
use serde::{Deserialize, Deserializer};
use std::any;
use std::fmt;
use std::str::FromStr;
use std::time::SystemTime;

/// Deserialize as `String`, then convert to T via [`FromStr`]
pub(crate) fn parse_str<'de, D, T>(deserializer: D) -> Result<T, D::Error>
where
    D: Deserializer<'de>,
    T: FromStr,
    <T as FromStr>::Err: fmt::Display,
{
    let s: String = Deserialize::deserialize(deserializer)?;
    s.parse().map_err(|e| {
        Error::custom(format!(
            "Failed to parse string as {}: {e}",
            any::type_name::<T>(),
        ))
    })
}

pub mod structs {
    pub use dirs::Folder;
    pub use dirs::Folders;
    pub use domains::Domain;
    pub use domains::Domains;
    pub use files::Contents;
    pub use files::Resources;
    pub use unique_id::UniqueIds;

    pub mod domains {
        use super::super::*;
        use super::common::*;

        /// Domain endpoint
        #[derive(Deserialize)]
        pub struct Domains {
            #[serde(rename = "data")]
            pub domains: Vec<Domain>,
            #[serde(rename = "_links")]
            links: Links,
        }

        impl Domains {
            pub fn has_more(&self) -> bool {
                self.links.has_more()
            }
        }

        #[derive(Deserialize)]
        pub struct Domain {
            #[serde(deserialize_with = "parse_str")]
            key: u64,
            version: u64,
            paths: Paths,
        }

        impl Domain {
            pub fn key(&self) -> u64 {
                self.key
            }

            pub fn version(&self) -> u64 {
                self.version
            }

            pub fn name(&self) -> FileName {
                FileName::from_tocco_name(&self.paths.label.value)
            }

            pub fn mtime(&self) -> SystemTime {
                self.paths.update_timestamp.value.into()
            }
        }

        #[derive(Deserialize)]
        struct Paths {
            label: StringValue,
            update_timestamp: TsValue,
        }
    }

    pub mod dirs {
        use super::super::*;
        use super::common::*;

        #[derive(Deserialize)]
        pub struct Folders {
            #[serde(rename = "data")]
            pub folders: Vec<Folder>,

            #[serde(rename = "_links")]
            links: Links,
        }

        impl Folders {
            pub fn has_more(&self) -> bool {
                self.links.has_more()
            }
        }

        #[derive(Deserialize)]
        pub struct Folder {
            #[serde(deserialize_with = "parse_str")]
            key: u64,
            version: u64,
            paths: Paths,
        }

        impl Folder {
            pub fn key(&self) -> u64 {
                self.key
            }

            pub fn version(&self) -> u64 {
                self.version
            }

            pub fn name(&self) -> FileName {
                FileName::from_tocco_name(&self.paths.label.value)
            }

            pub fn name_lost_and_found_folder(&self) -> FileName {
                FileName::from_tocco_name_lost_and_found_folder(&self.paths.label.value, self.key())
            }

            pub fn mtime(&self) -> SystemTime {
                self.paths.update_timestamp.value.into()
            }
        }

        #[derive(Deserialize)]
        struct Paths {
            label: StringValue,
            update_timestamp: TsValue,
        }
    }

    pub mod files {
        use super::super::*;
        use super::common::*;

        #[derive(Deserialize)]
        pub struct Resources {
            #[serde(rename = "data")]
            pub resources: Vec<Resource>,

            #[serde(rename = "_links")]
            links: Links,
        }

        impl Resources {
            pub fn has_more(&self) -> bool {
                self.links.has_more()
            }
        }

        #[derive(Deserialize)]
        pub struct Resource {
            #[serde(deserialize_with = "parse_str")]
            key: u64,
            version: u64,
            paths: Paths,
        }

        impl Resource {
            pub fn key(&self) -> u64 {
                self.key
            }

            pub fn version(&self) -> u64 {
                self.version
            }

            pub fn name(&self) -> FileName {
                FileName::from_tocco_name(&self.paths.label.value)
            }

            pub fn name_lost_and_found_file(&self) -> FileName {
                FileName::from_tocco_name_lost_and_found_file(&self.paths.label.value, self.key())
            }

            pub fn mtime(&self) -> SystemTime {
                self.paths
                    .rel_content
                    .value
                    .paths
                    .update_timestamp
                    .value
                    .into()
            }

            pub fn size(&self) -> u64 {
                self.paths.rel_content.value.paths.data.value.size
            }
        }

        #[derive(Deserialize)]
        struct Paths {
            label: StringValue,
            #[serde(rename = "relContent")]
            rel_content: RelContent,
        }

        #[derive(Deserialize)]
        struct RelContent {
            value: Content,
        }

        #[derive(Deserialize)]
        pub struct Contents {
            #[serde(rename = "data")]
            pub contents: Vec<Content>,
        }

        impl Content {
            pub fn size(&self) -> u64 {
                self.paths.data.value.size
            }

            pub fn mtime(&self) -> SystemTime {
                self.paths.update_timestamp.value.into()
            }

            pub fn into_url(self) -> String {
                self.paths.data.value.url
            }
        }

        #[derive(Deserialize)]
        pub struct Content {
            paths: ContentPaths,
        }

        #[derive(Deserialize)]
        struct ContentPaths {
            data: Data,
            update_timestamp: TsValue,
        }

        #[derive(Deserialize)]
        struct Data {
            value: DataValue,
        }

        #[derive(Deserialize)]
        struct DataValue {
            #[serde(rename = "sizeInBytes")]
            size: u64,

            #[serde(rename = "binaryLink")]
            url: String,
        }
    }

    pub mod unique_id {
        use super::super::*;
        use super::common::*;

        #[derive(Deserialize)]
        pub struct UniqueIds {
            #[serde(rename = "data")]
            unique_ids: Vec<UniqueId>,
        }

        impl UniqueIds {
            pub fn unique_ids(self) -> Vec<String> {
                self.unique_ids
                    .into_iter()
                    .map(|entry| entry.paths.unique_id.value)
                    .collect()
            }
        }

        #[derive(Deserialize)]
        struct UniqueId {
            paths: Paths,
        }

        #[derive(Deserialize)]
        struct Paths {
            unique_id: StringValue,
        }
    }

    pub mod common {
        use super::super::*;

        /// String value
        #[derive(Deserialize)]
        pub struct StringValue {
            pub value: String,
        }

        /// Timestamp value
        #[derive(Deserialize)]
        pub struct TsValue {
            pub value: DateTime<chrono::Local>,
        }

        /// "_links" section
        #[derive(Deserialize)]
        pub struct Links {
            next: Option<Ignored>,
        }

        impl Links {
            pub fn has_more(&self) -> bool {
                self.next.is_some()
            }
        }

        #[derive(Deserialize)]
        struct Ignored {}
    }
}
