/*
 * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <hello@stalw.art>
 *
 * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
 */

use std::fmt;

use crate::{
    object::{email::EmailComparator, file_node::FileNodeComparator},
    response::serialize::serialize_hex,
    types::date::UTCDate,
};
use ahash::AHashMap;
use serde::{Deserialize, Deserializer};
use types::{id::Id, type_state::DataType};
use utils::map::vec_map::VecMap;

#[derive(Debug, Clone, serde::Serialize)]
pub struct Session {
    #[serde(rename(serialize = "capabilities"))]
    pub capabilities: VecMap<Capability, Capabilities>,
    #[serde(rename(serialize = "accounts"))]
    pub accounts: VecMap<Id, Account>,
    #[serde(rename(serialize = "primaryAccounts"))]
    pub primary_accounts: VecMap<Capability, Id>,
    #[serde(rename(serialize = "username"))]
    pub username: String,
    #[serde(rename(serialize = "apiUrl"))]
    pub api_url: String,
    #[serde(rename(serialize = "downloadUrl"))]
    pub download_url: String,
    #[serde(rename(serialize = "uploadUrl"))]
    pub upload_url: String,
    #[serde(rename(serialize = "eventSourceUrl"))]
    pub event_source_url: String,
    #[serde(rename(serialize = "state"))]
    #[serde(serialize_with = "serialize_hex")]
    pub state: u32,
    #[serde(skip)]
    pub base_url: String,
}

#[derive(Debug, Clone, serde::Serialize)]
pub struct Account {
    #[serde(rename(serialize = "name"))]
    pub name: String,
    #[serde(rename(serialize = "isPersonal"))]
    pub is_personal: bool,
    #[serde(rename(serialize = "isReadOnly"))]
    pub is_read_only: bool,
    #[serde(rename(serialize = "accountCapabilities"))]
    pub account_capabilities: VecMap<Capability, Capabilities>,
}

#[derive(Debug, Clone, Copy, serde::Serialize, Hash, PartialEq, Eq, PartialOrd, Ord)]
pub enum Capability {
    #[serde(rename(serialize = "urn:ietf:params:jmap:core"))]
    Core = 1 << 0,
    #[serde(rename(serialize = "urn:ietf:params:jmap:mail"))]
    Mail = 1 << 1,
    #[serde(rename(serialize = "urn:ietf:params:jmap:submission"))]
    Submission = 1 << 2,
    #[serde(rename(serialize = "urn:ietf:params:jmap:vacationresponse"))]
    VacationResponse = 1 << 3,
    #[serde(rename(serialize = "urn:ietf:params:jmap:contacts"))]
    Contacts = 1 << 4,
    #[serde(rename(serialize = "urn:ietf:params:jmap:contacts:parse"))]
    ContactsParse = 1 << 5,
    #[serde(rename(serialize = "urn:ietf:params:jmap:calendars"))]
    Calendars = 1 << 6,
    #[serde(rename(serialize = "urn:ietf:params:jmap:calendars:parse"))]
    CalendarsParse = 1 << 7,
    #[serde(rename(serialize = "urn:ietf:params:jmap:websocket"))]
    WebSocket = 1 << 8,
    #[serde(rename(serialize = "urn:ietf:params:jmap:sieve"))]
    Sieve = 1 << 9,
    #[serde(rename(serialize = "urn:ietf:params:jmap:blob"))]
    Blob = 1 << 10,
    #[serde(rename(serialize = "urn:ietf:params:jmap:quota"))]
    Quota = 1 << 11,
    #[serde(rename(serialize = "urn:ietf:params:jmap:principals"))]
    Principals = 1 << 12,
    #[serde(rename(serialize = "urn:ietf:params:jmap:principals:owner"))]
    PrincipalsOwner = 1 << 13,
    #[serde(rename(serialize = "urn:ietf:params:jmap:principals:availability"))]
    PrincipalsAvailability = 1 << 14,
    #[serde(rename(serialize = "urn:ietf:params:jmap:filenode"))]
    FileNode = 1 << 15,
    #[serde(rename(serialize = "urn:ietf:params:jmap:mail:share"))]
    MailShare = 1 << 16,
    #[serde(rename(serialize = "urn:stalwart:jmap"))]
    Stalwart = 1 << 17,
}

#[derive(Debug, Clone, Copy, Default)]
#[repr(transparent)]
pub struct CapabilityIds(pub u32);

impl CapabilityIds {
    pub fn contains(&self, capability: Capability) -> bool {
        self.0 & capability as u32 != 0
    }
}

#[derive(Debug, Clone, serde::Serialize)]
#[serde(untagged)]
#[allow(dead_code)]
pub enum Capabilities {
    Core(CoreCapabilities),
    Mail(MailCapabilities),
    Submission(SubmissionCapabilities),
    WebSocket(WebSocketCapabilities),
    SieveAccount(SieveAccountCapabilities),
    SieveSession(SieveSessionCapabilities),
    Blob(BlobCapabilities),
    Contacts(ContactsCapabilities),
    Principals(PrincipalCapabilities),
    PrincipalsAvailability(PrincipalAvailabilityCapabilities),
    Calendar(CalendarCapabilities),
    FileNode(FileNodeCapabilities),
    Empty(EmptyCapabilities),
}

#[derive(Debug, Clone, serde::Serialize)]
pub struct CoreCapabilities {
    #[serde(rename(serialize = "maxSizeUpload"))]
    pub max_size_upload: u64,
    #[serde(rename(serialize = "maxConcurrentUpload"))]
    pub max_concurrent_upload: u64,
    #[serde(rename(serialize = "maxSizeRequest"))]
    pub max_size_request: u64,
    #[serde(rename(serialize = "maxConcurrentRequests"))]
    pub max_concurrent_requests: u64,
    #[serde(rename(serialize = "maxCallsInRequest"))]
    pub max_calls_in_request: u64,
    #[serde(rename(serialize = "maxObjectsInGet"))]
    pub max_objects_in_get: u64,
    #[serde(rename(serialize = "maxObjectsInSet"))]
    pub max_objects_in_set: u64,
    #[serde(rename(serialize = "collationAlgorithms"))]
    pub collation_algorithms: Vec<String>,
}

#[derive(Debug, Clone, serde::Serialize)]
pub struct WebSocketCapabilities {
    #[serde(rename(serialize = "url"))]
    pub url: String,
    #[serde(rename(serialize = "supportsPush"))]
    pub supports_push: bool,
}

#[derive(Debug, Clone, serde::Serialize)]
pub struct SieveSessionCapabilities {
    #[serde(rename(serialize = "implementation"))]
    pub implementation: &'static str,
}

#[derive(Debug, Clone, serde::Serialize)]
pub struct SieveAccountCapabilities {
    #[serde(rename(serialize = "maxSizeScriptName"))]
    pub max_script_name: u64,
    #[serde(rename(serialize = "maxSizeScript"))]
    pub max_script_size: u64,
    #[serde(rename(serialize = "maxNumberScripts"))]
    pub max_scripts: u64,
    #[serde(rename(serialize = "maxNumberRedirects"))]
    pub max_redirects: u64,
    #[serde(rename(serialize = "sieveExtensions"))]
    pub extensions: Vec<String>,
    #[serde(rename(serialize = "notificationMethods"))]
    pub notification_methods: Option<Vec<String>>,
    #[serde(rename(serialize = "externalLists"))]
    pub ext_lists: Option<Vec<String>>,
}

#[derive(Debug, Clone, serde::Serialize)]
pub struct MailCapabilities {
    #[serde(rename(serialize = "maxMailboxesPerEmail"))]
    pub max_mailboxes_per_email: Option<u64>,
    #[serde(rename(serialize = "maxMailboxDepth"))]
    pub max_mailbox_depth: u64,
    #[serde(rename(serialize = "maxSizeMailboxName"))]
    pub max_size_mailbox_name: u64,
    #[serde(rename(serialize = "maxSizeAttachmentsPerEmail"))]
    pub max_size_attachments_per_email: u64,
    #[serde(rename(serialize = "emailQuerySortOptions"))]
    pub email_query_sort_options: Vec<EmailComparator>,
    #[serde(rename(serialize = "mayCreateTopLevelMailbox"))]
    pub may_create_top_level_mailbox: bool,
}

#[derive(Debug, Clone, serde::Serialize)]
pub struct SubmissionCapabilities {
    #[serde(rename(serialize = "maxDelayedSend"))]
    pub max_delayed_send: u64,
    #[serde(rename(serialize = "submissionExtensions"))]
    pub submission_extensions: VecMap<String, Vec<String>>,
}

#[derive(Debug, Clone, serde::Serialize)]
pub struct BlobCapabilities {
    #[serde(rename(serialize = "maxSizeBlobSet"))]
    pub max_size_blob_set: u64,
    #[serde(rename(serialize = "maxDataSources"))]
    pub max_data_sources: u64,
    #[serde(rename(serialize = "supportedTypeNames"))]
    pub supported_type_names: Vec<DataType>,
    #[serde(rename(serialize = "supportedDigestAlgorithms"))]
    pub supported_digest_algorithms: Vec<&'static str>,
}

#[derive(Debug, Clone, serde::Serialize)]
pub struct CalendarCapabilities {
    #[serde(rename(serialize = "maxCalendarsPerEvent"))]
    pub max_calendars_per_event: Option<u64>,
    #[serde(rename(serialize = "minDateTime"))]
    pub min_date_time: UTCDate,
    #[serde(rename(serialize = "maxDateTime"))]
    pub max_date_time: UTCDate,
    #[serde(rename(serialize = "maxExpandedQueryDuration"))]
    pub max_expanded_query_duration: String,
    #[serde(rename(serialize = "maxParticipantsPerEvent"))]
    pub max_participants_per_event: Option<u64>,
    #[serde(rename(serialize = "mayCreateCalendar"))]
    pub may_create_calendar: bool,
}

#[derive(Debug, Clone, serde::Serialize)]
pub struct ContactsCapabilities {
    #[serde(rename(serialize = "maxAddressBooksPerCard"))]
    pub max_address_books_per_card: Option<u64>,
    #[serde(rename(serialize = "mayCreateAddressBook"))]
    pub may_create_address_book: bool,
}

#[derive(Debug, Clone, serde::Serialize)]
pub struct PrincipalAvailabilityCapabilities {
    #[serde(rename(serialize = "maxAvailabilityDuration"))]
    pub max_availability_duration: String,
}

#[derive(Debug, Clone, serde::Serialize)]
pub struct PrincipalCapabilities {
    #[serde(rename(serialize = "currentUserPrincipalId"))]
    pub current_user_principal_id: Option<Id>,
}

/*#[derive(Debug, Clone, serde::Serialize)]
pub struct PrincipalOwnerCapabilities {
    #[serde(rename(serialize = "accountIdForPrincipal"))]
    pub account_id_for_principal: Id,

    #[serde(rename(serialize = "principalId"))]
    pub principal_id: Id,
}

#[derive(Debug, Clone, serde::Serialize)]
pub struct PrincipalCalendarCapabilities {
    #[serde(rename(serialize = "accountIdForPrincipal"))]
    pub account_id_for_principal: Option<Id>,
    #[serde(rename(serialize = "mayGetAvailability"))]
    pub may_get_availability: bool,
    #[serde(rename(serialize = "mayShareWith"))]
    pub may_share_with: bool,
    #[serde(rename(serialize = "calendarAddress"))]
    pub calendar_address: String,
}*/

#[derive(Debug, Clone, serde::Serialize)]
pub struct FileNodeCapabilities {
    #[serde(rename(serialize = "maxFileNodeDepth"))]
    pub max_file_node_depth: Option<u64>,
    #[serde(rename(serialize = "maxSizeFileNodeName"))]
    pub max_size_file_node_name: u64,
    #[serde(rename(serialize = "forbiddenNameChars"))]
    pub forbidden_name_chars: Option<String>,
    #[serde(rename(serialize = "forbiddenNodeNames"))]
    pub forbidden_node_names: Option<Vec<String>>,
    #[serde(rename(serialize = "fileNodeQuerySortOptions"))]
    pub file_node_query_sort_options: Vec<FileNodeComparator>,
    #[serde(rename(serialize = "mayCreateTopLevelFileNode"))]
    pub may_create_top_level_file_node: bool,
    #[serde(rename(serialize = "caseInsensitiveNames"))]
    pub case_insensitive_names: bool,
    #[serde(rename(serialize = "webTrashUrl"))]
    pub web_trash_url: Option<String>,
    #[serde(rename(serialize = "webUrlTemplate"))]
    pub web_url_template: Option<String>,
    #[serde(rename(serialize = "webWriteUrlTemplate"))]
    pub web_write_url_template: Option<String>,
}

#[derive(Debug, Clone, Default, serde::Serialize)]
pub struct EmptyCapabilities {}

#[derive(Default, Clone)]
pub struct BaseCapabilities {
    pub session: VecMap<Capability, Capabilities>,
    pub account: AHashMap<Capability, Capabilities>,
}

impl Capability {
    pub fn as_str(&self) -> &'static str {
        match self {
            Capability::Core => "urn:ietf:params:jmap:core",
            Capability::Mail => "urn:ietf:params:jmap:mail",
            Capability::Submission => "urn:ietf:params:jmap:submission",
            Capability::VacationResponse => "urn:ietf:params:jmap:vacationresponse",
            Capability::Contacts => "urn:ietf:params:jmap:contacts",
            Capability::ContactsParse => "urn:ietf:params:jmap:contacts:parse",
            Capability::Calendars => "urn:ietf:params:jmap:calendars",
            Capability::CalendarsParse => "urn:ietf:params:jmap:calendars:parse",
            Capability::WebSocket => "urn:ietf:params:jmap:websocket",
            Capability::Sieve => "urn:ietf:params:jmap:sieve",
            Capability::Blob => "urn:ietf:params:jmap:blob",
            Capability::Quota => "urn:ietf:params:jmap:quota",
            Capability::Principals => "urn:ietf:params:jmap:principals",
            Capability::PrincipalsOwner => "urn:ietf:params:jmap:principals:owner",
            Capability::PrincipalsAvailability => "urn:ietf:params:jmap:principals:availability",
            Capability::FileNode => "urn:ietf:params:jmap:filenode",
            Capability::MailShare => "urn:ietf:params:jmap:mail:share",
            Capability::Stalwart => "urn:stalwart:jmap",
        }
    }

    pub fn all_capabilities() -> &'static [Capability] {
        &[
            Capability::Core,
            Capability::Mail,
            Capability::Submission,
            Capability::VacationResponse,
            Capability::Contacts,
            Capability::ContactsParse,
            Capability::Calendars,
            Capability::CalendarsParse,
            Capability::WebSocket,
            Capability::Sieve,
            Capability::Blob,
            Capability::Quota,
            Capability::Principals,
            Capability::PrincipalsAvailability,
            Capability::FileNode,
            Capability::MailShare,
            Capability::Stalwart,
        ]
    }
}

impl Session {
    pub fn new(base_url: impl Into<String>, base_capabilities: &BaseCapabilities) -> Session {
        let base_url = base_url.into();
        let mut capabilities = base_capabilities.session.clone();
        capabilities.append(
            Capability::WebSocket,
            Capabilities::WebSocket(WebSocketCapabilities::new(&base_url)),
        );

        Session {
            capabilities,
            accounts: VecMap::new(),
            primary_accounts: VecMap::new(),
            username: "".to_string(),
            api_url: format!("{}/jmap/", base_url),
            download_url: format!(
                "{}/jmap/download/{{accountId}}/{{blobId}}/{{name}}?accept={{type}}",
                base_url
            ),
            upload_url: format!("{}/jmap/upload/{{accountId}}/", base_url),
            event_source_url: format!(
                "{}/jmap/eventsource/?types={{types}}&closeafter={{closeafter}}&ping={{ping}}",
                base_url
            ),
            base_url,
            state: 0,
        }
    }

    pub fn set_state(&mut self, state: u32) {
        self.state = state;
    }

    pub fn api_url(&self) -> &str {
        &self.api_url
    }

    pub fn base_url(&self) -> &str {
        &self.base_url
    }
}

impl Default for SieveSessionCapabilities {
    fn default() -> Self {
        Self {
            implementation: "Stalwart v1.0.0",
        }
    }
}

impl WebSocketCapabilities {
    pub fn new(base_url: &str) -> Self {
        WebSocketCapabilities {
            url: format!(
                "ws{}/jmap/ws",
                base_url.strip_prefix("http").unwrap_or_default()
            ),
            supports_push: true,
        }
    }
}

impl Capabilities {
    pub fn to_account_capabilities(
        &self,
        current_user_principal_id: Option<Id>,
        may_create: bool,
    ) -> Capabilities {
        match self {
            Capabilities::Contacts(contacts_capabilities) => {
                Capabilities::Contacts(ContactsCapabilities {
                    may_create_address_book: may_create,
                    ..contacts_capabilities.clone()
                })
            }
            Capabilities::Principals(_) => Capabilities::Principals(PrincipalCapabilities {
                current_user_principal_id,
            }),
            Capabilities::Calendar(calendar_capabilities) => {
                Capabilities::Calendar(CalendarCapabilities {
                    may_create_calendar: may_create,
                    ..calendar_capabilities.clone()
                })
            }
            Capabilities::FileNode(file_node_capabilities) => {
                Capabilities::FileNode(FileNodeCapabilities {
                    may_create_top_level_file_node: may_create,
                    ..file_node_capabilities.clone()
                })
            }
            _ => self.clone(),
        }
    }
}

impl Capability {
    pub fn parse(s: &str) -> Option<Self> {
        hashify::tiny_map!(s.as_bytes(),
            "urn:ietf:params:jmap:core" => Capability::Core,
            "urn:ietf:params:jmap:mail" => Capability::Mail,
            "urn:ietf:params:jmap:submission" => Capability::Submission,
            "urn:ietf:params:jmap:vacationresponse" => Capability::VacationResponse,
            "urn:ietf:params:jmap:contacts" => Capability::Contacts,
            "urn:ietf:params:jmap:calendars" => Capability::Calendars,
            "urn:ietf:params:jmap:websocket" => Capability::WebSocket,
            "urn:ietf:params:jmap:sieve" => Capability::Sieve,
            "urn:ietf:params:jmap:blob" => Capability::Blob,
            "urn:ietf:params:jmap:quota" => Capability::Quota,
            "urn:ietf:params:jmap:principals" => Capability::Principals,
            "urn:ietf:params:jmap:principals:owner" => Capability::PrincipalsOwner,
            "urn:ietf:params:jmap:filenode" => Capability::FileNode,
            "urn:ietf:params:jmap:principals:availability" => Capability::PrincipalsAvailability,
            "urn:ietf:params:jmap:contacts:parse" => Capability::ContactsParse,
            "urn:ietf:params:jmap:calendars:parse" => Capability::CalendarsParse,
            "urn:ietf:params:jmap:mail:share" => Capability::MailShare,
            "urn:stalwart:jmap" => Capability::Stalwart,
        )
    }
}

impl<'de> Deserialize<'de> for CapabilityIds {
    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
    where
        D: Deserializer<'de>,
    {
        struct CapabilityIdsVisitor;

        impl<'de> serde::de::Visitor<'de> for CapabilityIdsVisitor {
            type Value = CapabilityIds;

            fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
                formatter.write_str("an array of capability strings")
            }

            fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
            where
                A: serde::de::SeqAccess<'de>,
            {
                let mut capability_flags = 0u32;

                while let Some(capability_str) = seq.next_element::<&str>()? {
                    let capability = Capability::parse(capability_str).ok_or_else(|| {
                        serde::de::Error::custom(format!("Unknown capability: {capability_str:?}"))
                    })?;

                    capability_flags |= capability as u32;
                }

                Ok(CapabilityIds(capability_flags))
            }
        }

        deserializer.deserialize_seq(CapabilityIdsVisitor)
    }
}
