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

use std::io::Cursor;

use self::{mailstore::jmap::JmapConfig, smtp::SmtpConfig, storage::Storage};
use crate::{
    Core, Network,
    auth::oauth::config::OAuthConfig,
    config::mailstore::{
        email::EmailConfig, imap::ImapConfig, scripts::Scripting, spamfilter::SpamFilterConfig,
    },
};
use arc_swap::ArcSwap;
use groupware::GroupwareConfig;
use hyper::HeaderMap;
use ring::signature::{EcdsaKeyPair, RsaKeyPair};
use store::registry::bootstrap::Bootstrap;
use telemetry::Metrics;

pub mod groupware;
pub mod inner;
pub mod mailstore;
pub mod network;
pub mod server;
pub mod smtp;
pub mod storage;
pub mod telemetry;

impl Core {
    pub async fn parse(bp: &mut Bootstrap, mut storage: Storage) -> Self {
        // SPDX-SnippetBegin
        // SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <hello@stalw.art>
        // SPDX-License-Identifier: LicenseRef-SEL
        #[cfg(feature = "enterprise")]
        let enterprise = {
            let enterprise = crate::enterprise::Enterprise::parse(bp).await;
            if enterprise.is_none() && !bp.registry.is_recovery_mode() {
                use registry::schema::prelude::ObjectType;
                use store::Store;

                if storage.data.is_enterprise() {
                    bp.build_error(
                        ObjectType::DataStore.singleton(),
                        "Disabling enterprise-only data store.",
                    );
                    storage.data = storage.data.downgrade_store();
                }
                if storage.blob.is_enterprise() {
                    bp.build_error(
                        ObjectType::BlobStore.singleton(),
                        "Disabling enterprise-only blob store.",
                    );
                    storage.blob = storage.blob.downgrade_store();
                }
                if storage.memory.is_enterprise() {
                    bp.build_error(
                        ObjectType::InMemoryStore.singleton(),
                        "Disabling enterprise-only in-memory store.",
                    );
                    storage.memory = storage.memory.downgrade_store();
                }
                storage.metrics = Store::None;
                storage.tracing = Store::None;
                storage.directories.clear();
            }
            enterprise
        };
        // SPDX-SnippetEnd

        Self {
            // SPDX-SnippetBegin
            // SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <hello@stalw.art>
            // SPDX-License-Identifier: LicenseRef-SEL
            #[cfg(feature = "enterprise")]
            enterprise,
            // SPDX-SnippetEnd
            sieve: Scripting::parse(bp).await,
            network: Network::parse(bp).await,
            smtp: Box::pin(SmtpConfig::parse(bp)).await,
            jmap: JmapConfig::parse(bp).await,
            imap: ImapConfig::parse(bp).await,
            oauth: OAuthConfig::parse(bp).await,
            metrics: Metrics::parse(bp).await,
            spam: SpamFilterConfig::parse(bp).await,
            email: EmailConfig::parse(bp).await,
            groupware: GroupwareConfig::parse(bp).await,
            storage,
        }
    }

    pub fn into_shared(self) -> ArcSwap<Self> {
        ArcSwap::from_pointee(self)
    }
}

pub fn build_rsa_keypair(pem: &str) -> Result<RsaKeyPair, String> {
    for item in rustls_pemfile::read_all(&mut Cursor::new(pem)) {
        match item.map_err(|err| format!("Failed to read private key PEM: {err}"))? {
            rustls_pemfile::Item::Pkcs1Key(key) => {
                return RsaKeyPair::from_der(key.secret_pkcs1_der())
                    .map_err(|err| format!("Failed to parse PKCS1 RSA key: {err}"));
            }
            rustls_pemfile::Item::Pkcs8Key(key) => {
                return RsaKeyPair::from_pkcs8(key.secret_pkcs8_der())
                    .map_err(|err| format!("Failed to parse PKCS8 RSA key: {err}"));
            }
            _ => continue, // Skip certificates, DH params, etc.
        }
    }

    Err("No RSA key found in PEM".to_string())
}

pub fn build_ecdsa_pem(
    alg: &'static ring::signature::EcdsaSigningAlgorithm,
    pem: &str,
) -> Result<EcdsaKeyPair, String> {
    for item in rustls_pemfile::read_all(&mut Cursor::new(pem)) {
        match item.map_err(|err| format!("Failed to read private key PEM: {err}"))? {
            rustls_pemfile::Item::Pkcs8Key(key) => {
                return EcdsaKeyPair::from_pkcs8(
                    alg,
                    key.secret_pkcs8_der(),
                    &ring::rand::SystemRandom::new(),
                )
                .map_err(|err| format!("Failed to parse PKCS8 ECDSA key: {err}"));
            }
            _ => continue, // Skip certificates, DH params, etc.
        }
    }

    Err("No ECDSA key found in PEM".to_string())
}
