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

use super::JMAPTest;
use crate::jmap::{assert_is_empty, mailbox::destroy_all_mailboxes, replace_blob_ids};
use ::email::mailbox::INBOX_ID;
use jmap_client::email::{self, Header, HeaderForm, import::EmailImportResponse};
use mail_parser::HeaderName;
use std::{fs, path::PathBuf};
use types::id::Id;

pub async fn test(params: &mut JMAPTest) {
    println!("Running Email Get tests...");
    let server = params.server.clone();

    let mut test_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
    test_dir.push("resources");
    test_dir.push("jmap");
    test_dir.push("email_get");

    let mailbox_id = Id::from(INBOX_ID).to_string();
    params.client.set_default_account_id(Id::from(1u64));

    for file_name in fs::read_dir(&test_dir).unwrap() {
        let mut file_name = file_name.as_ref().unwrap().path();
        if file_name.extension().is_none_or(|e| e != "eml") {
            continue;
        }
        let is_headers_test = file_name.file_name().unwrap() == "headers.eml";

        let blob = fs::read(&file_name).unwrap();
        let blob_len = blob.len();

        // Import email
        let mut request = params.client.build();
        let import_request = request
            .import_email()
            .account_id(Id::from(1u64).to_string())
            .email(
                params
                    .client
                    .upload(None, blob, None)
                    .await
                    .unwrap()
                    .take_blob_id(),
            )
            .mailbox_ids([mailbox_id.clone()])
            .keywords(["tag".to_string()])
            .received_at((blob_len * 1000000) as i64);
        let id = import_request.create_id();
        let mut response = request.send_single::<EmailImportResponse>().await.unwrap();
        assert_ne!(response.old_state(), Some(response.new_state()));
        let email = response.created(&id).unwrap();

        let mut request = params.client.build();
        request
            .get_email()
            .ids([email.id().unwrap()])
            .properties([
                email::Property::Id,
                email::Property::BlobId,
                email::Property::ThreadId,
                email::Property::MailboxIds,
                email::Property::Keywords,
                email::Property::Size,
                email::Property::ReceivedAt,
                email::Property::MessageId,
                email::Property::InReplyTo,
                email::Property::References,
                email::Property::Sender,
                email::Property::From,
                email::Property::To,
                email::Property::Cc,
                email::Property::Bcc,
                email::Property::ReplyTo,
                email::Property::Subject,
                email::Property::SentAt,
                email::Property::HasAttachment,
                email::Property::Preview,
                email::Property::BodyValues,
                email::Property::TextBody,
                email::Property::HtmlBody,
                email::Property::Attachments,
                email::Property::BodyStructure,
            ])
            .arguments()
            .body_properties(if !is_headers_test {
                [
                    email::BodyProperty::PartId,
                    email::BodyProperty::BlobId,
                    email::BodyProperty::Size,
                    email::BodyProperty::Name,
                    email::BodyProperty::Type,
                    email::BodyProperty::Charset,
                    email::BodyProperty::Headers,
                    email::BodyProperty::Disposition,
                    email::BodyProperty::Cid,
                    email::BodyProperty::Language,
                    email::BodyProperty::Location,
                ]
            } else {
                [
                    email::BodyProperty::PartId,
                    email::BodyProperty::Size,
                    email::BodyProperty::Name,
                    email::BodyProperty::Type,
                    email::BodyProperty::Charset,
                    email::BodyProperty::Disposition,
                    email::BodyProperty::Cid,
                    email::BodyProperty::Language,
                    email::BodyProperty::Location,
                    email::BodyProperty::Header(Header {
                        name: "X-Custom-Header".into(),
                        form: HeaderForm::Raw,
                        all: false,
                    }),
                    email::BodyProperty::Header(Header {
                        name: "X-Custom-Header-2".into(),
                        form: HeaderForm::Raw,
                        all: false,
                    }),
                ]
            })
            .fetch_all_body_values(true)
            .max_body_value_bytes(100);

        let mut result = request
            .send_get_email()
            .await
            .unwrap()
            .take_list()
            .pop()
            .unwrap()
            .into_test();

        if is_headers_test {
            for property in all_headers() {
                let mut request = params.client.build();
                request
                    .get_email()
                    .ids([email.id().unwrap()])
                    .properties([property]);
                result.headers.extend(
                    request
                        .send_get_email()
                        .await
                        .unwrap()
                        .take_list()
                        .pop()
                        .unwrap()
                        .into_test()
                        .headers,
                );
            }
        }

        let result = replace_blob_ids(serde_json::to_string_pretty(&result).unwrap());

        file_name.set_extension("json");

        if fs::read(&file_name).unwrap() != result.as_bytes() {
            file_name.set_extension("failed");
            fs::write(&file_name, result.as_bytes()).unwrap();
            panic!("Test failed, output saved to {}", file_name.display());
        }
    }

    destroy_all_mailboxes(params).await;
    assert_is_empty(server).await;
}

pub fn all_headers() -> Vec<email::Property> {
    let mut properties = Vec::new();

    for header in [
        HeaderName::From,
        HeaderName::To,
        HeaderName::Cc,
        HeaderName::Bcc,
        HeaderName::Other("X-Address-Single".into()),
        HeaderName::Other("X-Address".into()),
        HeaderName::Other("X-AddressList-Single".into()),
        HeaderName::Other("X-AddressList".into()),
        HeaderName::Other("X-AddressesGroup-Single".into()),
        HeaderName::Other("X-AddressesGroup".into()),
    ] {
        properties.push(email::Property::Header(Header {
            form: HeaderForm::Raw,
            name: header.as_str().to_string(),
            all: true,
        }));
        properties.push(email::Property::Header(Header {
            form: HeaderForm::Raw,
            name: header.as_str().to_string(),
            all: false,
        }));
        properties.push(email::Property::Header(Header {
            form: HeaderForm::Addresses,
            name: header.as_str().to_string(),
            all: true,
        }));
        properties.push(email::Property::Header(Header {
            form: HeaderForm::Addresses,
            name: header.as_str().to_string(),
            all: false,
        }));
        properties.push(email::Property::Header(Header {
            form: HeaderForm::GroupedAddresses,
            name: header.as_str().to_string(),
            all: true,
        }));
        properties.push(email::Property::Header(Header {
            form: HeaderForm::GroupedAddresses,
            name: header.as_str().to_string(),
            all: false,
        }));
    }

    for header in [
        HeaderName::ListPost,
        HeaderName::ListSubscribe,
        HeaderName::ListUnsubscribe,
        HeaderName::ListOwner,
        HeaderName::Other("X-List-Single".into()),
        HeaderName::Other("X-List".into()),
    ] {
        properties.push(email::Property::Header(Header {
            form: HeaderForm::Raw,
            name: header.as_str().to_string(),
            all: true,
        }));
        properties.push(email::Property::Header(Header {
            form: HeaderForm::Raw,
            name: header.as_str().to_string(),
            all: false,
        }));
        properties.push(email::Property::Header(Header {
            form: HeaderForm::URLs,
            name: header.as_str().to_string(),
            all: true,
        }));
        properties.push(email::Property::Header(Header {
            form: HeaderForm::URLs,
            name: header.as_str().to_string(),
            all: false,
        }));
    }

    for header in [
        HeaderName::Date,
        HeaderName::ResentDate,
        HeaderName::Other("X-Date-Single".into()),
        HeaderName::Other("X-Date".into()),
    ] {
        properties.push(email::Property::Header(Header {
            form: HeaderForm::Raw,
            name: header.as_str().to_string(),
            all: true,
        }));
        properties.push(email::Property::Header(Header {
            form: HeaderForm::Raw,
            name: header.as_str().to_string(),
            all: false,
        }));
        properties.push(email::Property::Header(Header {
            form: HeaderForm::Date,
            name: header.as_str().to_string(),
            all: true,
        }));
        properties.push(email::Property::Header(Header {
            form: HeaderForm::Date,
            name: header.as_str().to_string(),
            all: false,
        }));
    }

    for header in [
        HeaderName::MessageId,
        HeaderName::References,
        HeaderName::Other("X-Id-Single".into()),
        HeaderName::Other("X-Id".into()),
    ] {
        properties.push(email::Property::Header(Header {
            form: HeaderForm::Raw,
            name: header.as_str().to_string(),
            all: true,
        }));
        properties.push(email::Property::Header(Header {
            form: HeaderForm::Raw,
            name: header.as_str().to_string(),
            all: false,
        }));
        properties.push(email::Property::Header(Header {
            form: HeaderForm::MessageIds,
            name: header.as_str().to_string(),
            all: true,
        }));
        properties.push(email::Property::Header(Header {
            form: HeaderForm::MessageIds,
            name: header.as_str().to_string(),
            all: false,
        }));
    }

    for header in [
        HeaderName::Subject,
        HeaderName::Keywords,
        HeaderName::Other("X-Text-Single".into()),
        HeaderName::Other("X-Text".into()),
    ] {
        properties.push(email::Property::Header(Header {
            form: HeaderForm::Raw,
            name: header.as_str().to_string(),
            all: true,
        }));
        properties.push(email::Property::Header(Header {
            form: HeaderForm::Raw,
            name: header.as_str().to_string(),
            all: false,
        }));
        properties.push(email::Property::Header(Header {
            form: HeaderForm::Text,
            name: header.as_str().to_string(),
            all: true,
        }));
        properties.push(email::Property::Header(Header {
            form: HeaderForm::Text,
            name: header.as_str().to_string(),
            all: false,
        }));
    }

    properties
}
