import o from "@tutao/otest"
// @ts-ignore[untyped-import]
import en from "../../../src/mail-app/translations/en.js"
import type { UserController } from "../../../src/common/api/main/UserController.js"
import type { LoginController } from "../../../src/common/api/main/LoginController.js"
import {
	BodyTypeRef,
	Contact,
	ContactListTypeRef,
	ContactTypeRef,
	ConversationEntryTypeRef,
	createContact,
	Mail,
	MailAddressTypeRef,
	MailboxGroupRootTypeRef,
	MailboxPropertiesTypeRef,
	MailBoxTypeRef,
	MailDetailsDraftTypeRef,
	MailDetailsTypeRef,
	MailTypeRef,
	NotificationMailTypeRef,
	RecipientsTypeRef,
	TutanotaPropertiesTypeRef,
} from "../../../src/common/api/entities/tutanota/TypeRefs.js"
import { assertThrows, verify } from "@tutao/tutanota-test-utils"
import { downcast, isSameTypeRef } from "@tutao/tutanota-utils"
import {
	ChallengeTypeRef,
	CustomerTypeRef,
	GroupInfoTypeRef,
	GroupMembershipTypeRef,
	GroupTypeRef,
	UserTypeRef,
} from "../../../src/common/api/entities/sys/TypeRefs.js"
import { ConversationType, GroupType, MailMethod, OperationType } from "../../../src/common/api/common/TutanotaConstants.js"
import { lang, TranslationKey } from "../../../src/common/misc/LanguageViewModel.js"
import { EventController } from "../../../src/common/api/main/EventController.js"
import { UserError } from "../../../src/common/api/main/UserError.js"
import { EntityClient } from "../../../src/common/api/common/EntityClient.js"
import { isSameId } from "../../../src/common/api/common/utils/EntityUtils.js"
import { MailFacade } from "../../../src/common/api/worker/facades/lazy/MailFacade.js"
import { func, instance, matchers, object, replace, when } from "testdouble"
import { RecipientsModel } from "../../../src/common/api/main/RecipientsModel"
import { ResolvableRecipientMock } from "./ResolvableRecipientMock.js"
import { createTestEntity } from "../TestUtils.js"
import { ContactModel } from "../../../src/common/contactsFunctionality/ContactModel.js"
import { MailboxDetail, MailboxModel } from "../../../src/common/mailFunctionality/MailboxModel.js"
import { SendMailModel, TOO_MANY_VISIBLE_RECIPIENTS } from "../../../src/common/mailFunctionality/SendMailModel.js"
import { RecipientField } from "../../../src/common/mailFunctionality/SharedMailUtils.js"
import { getContactDisplayName } from "../../../src/common/contactsFunctionality/ContactUtils.js"
import { EntityUpdateData, PrefetchStatus } from "../../../src/common/api/common/utils/EntityUpdateUtils"
import { ConfigurationDatabase } from "../../../src/common/api/worker/facades/lazy/ConfigurationDatabase"
import { SyncTracker } from "../../../src/common/api/main/SyncTracker"
import { DateProvider } from "../../../src/common/api/common/DateProvider"
import { ProgrammingError } from "../../../src/common/api/common/error/ProgrammingError"

const { anything, argThat } = matchers

type TestIdGenerator = {
	currentIdValue: number
	currentListIdValue: number
	newId: () => Id
	newListId: () => Id
	newIdTuple: () => IdTuple
}
let testIdGenerator: TestIdGenerator = {
	currentIdValue: 0,
	currentListIdValue: 0,

	newId(): Id {
		return (this.currentIdValue++).toString()
	},

	newListId(): Id {
		return (this.currentListIdValue++).toString()
	},

	newIdTuple(): IdTuple {
		return [this.newListId(), this.newId()]
	},
}

const EXTERNAL_ADDRESS_1 = "address1@test.com"
const EXTERNAL_ADDRESS_2 = "address2@test.com"
const DEFAULT_SENDER_FOR_TESTING = "test@tutanota.de"
const INTERNAL_RECIPIENT_1 = {
	name: "test1",
	address: "test1@tutanota.de",
	contact: null,
}
const BODY_TEXT_1 = "lorem ipsum dolor yaddah yaddah"
const SUBJECT_LINE_1 = "Did you get that thing I sent ya"
const STRONG_PASSWORD = "@()IE!)(@FME)0-123jfDSA32SDACmmnvnvddEW"
const WEAK_PASSWORD = "123"
const noPatchesAndInstance: Pick<EntityUpdateData, "instance" | "patches"> = {
	instance: null,
	patches: null,
}

o.spec("SendMailModel", function () {
	o.before(function () {
		// we need lang initialized because the SendMailModel constructor requires some translation
		lang.init(en)
	})

	let mailboxModel: MailboxModel, entity: EntityClient, mailFacade: MailFacade, recipientsModel: RecipientsModel

	let model: SendMailModel
	let db: ConfigurationDatabase
	let syncTracker: SyncTracker
	let now: number

	o.beforeEach(function () {
		now = 0
		entity = instance(EntityClient)
		when(
			entity.loadRoot(
				argThat((typeRef) => isSameTypeRef(typeRef, ContactListTypeRef)),
				anything(),
			),
		).thenDo(() => ({ contacts: testIdGenerator.newId() }))
		when(entity.load(anything(), anything(), anything())).thenDo((typeRef, id, params) => ({
			_type: typeRef,
			_id: id,
		}))

		mailboxModel = instance(MailboxModel)

		const contactModel = object<ContactModel>()
		when(contactModel.getContactListId()).thenResolve("contactListId")
		when(contactModel.searchForContact(anything())).thenResolve(null)

		mailFacade = instance(MailFacade)
		when(mailFacade.createDraft(anything())).thenDo(() => createTestEntity(MailTypeRef))
		when(mailFacade.updateDraft(anything())).thenDo(() => createTestEntity(MailTypeRef))
		when(mailFacade.getRecipientKeyData(anything())).thenResolve(null)
		when(mailFacade.getAttachmentIds(anything())).thenResolve([])

		const tutanotaProperties = createTestEntity(TutanotaPropertiesTypeRef, {
			defaultSender: DEFAULT_SENDER_FOR_TESTING,
			defaultUnconfidential: true,
			notificationMailLanguage: "en",
			noAutomaticContacts: false,
		})
		const user = createTestEntity(UserTypeRef, {
			userGroup: createTestEntity(GroupMembershipTypeRef, {
				_id: testIdGenerator.newId(),
				group: testIdGenerator.newId(),
			}),
			memberships: [
				createTestEntity(GroupMembershipTypeRef, {
					_id: testIdGenerator.newId(),
					groupType: GroupType.Contact,
				}),
			],
		})

		const userController = object<UserController>()
		replace(userController, "user", user)
		replace(userController, "props", tutanotaProperties)
		when(userController.loadCustomer()).thenResolve(createTestEntity(CustomerTypeRef))

		const loginController = object<LoginController>()
		when(loginController.isInternalUserLoggedIn()).thenReturn(true)
		when(loginController.getUserController()).thenReturn(userController)

		const eventController = instance(EventController)

		const mailboxDetails: MailboxDetail = {
			mailbox: createTestEntity(MailBoxTypeRef),
			mailGroupInfo: createTestEntity(GroupInfoTypeRef, {
				mailAddress: "mailgroup@addre.ss",
			}),
			mailGroup: createTestEntity(GroupTypeRef),
			mailboxGroupRoot: createTestEntity(MailboxGroupRootTypeRef),
		}

		recipientsModel = instance(RecipientsModel)
		when(recipientsModel.initialize(anything())).thenDo((recipient) => {
			return new ResolvableRecipientMock(recipient.address, recipient.name, recipient.contact, recipient.type, [INTERNAL_RECIPIENT_1.address], [], user)
		})

		db = object()
		syncTracker = object()

		const dateProvider: DateProvider = {
			now(): number {
				return now
			},
			timeZone(): string {
				throw new ProgrammingError("timeZone was called when it shouldn't have")
			},
		}

		const mailboxProperties = createTestEntity(MailboxPropertiesTypeRef)
		model = new SendMailModel(
			mailFacade,
			entity,
			loginController,
			mailboxModel,
			contactModel,
			eventController,
			mailboxDetails,
			recipientsModel,
			dateProvider,
			mailboxProperties,
			db,

			async (mail: Mail) => {
				return false
			},
			syncTracker,
		)

		replace(model, "getDefaultSender", () => DEFAULT_SENDER_FOR_TESTING)
	})

	o.spec("initialization", function () {
		o("initWithTemplate empty", async function () {
			await model.initWithTemplate({}, "", "", [], false)
			o(model.getConversationType()).equals(ConversationType.NEW)
			o(model.getSubject()).equals("")
			o(model.getBody()).equals("")
			o(model.getDraft()).equals(null)
			o(model.allRecipients().length).equals(0)
			o(model.getSender()).equals(DEFAULT_SENDER_FOR_TESTING)
			o(model.isConfidential()).equals(true)
			o(model.containsExternalRecipients()).equals(false)
			o(model.getAttachments().length).equals(0)
			o(model.hasMailChanged()).equals(false)("initialization should not flag mail changed")
		})
		o("initWithTemplate data", async function () {
			const initializedModel = await model.initWithTemplate(
				{
					to: [INTERNAL_RECIPIENT_1],
				},
				SUBJECT_LINE_1,
				BODY_TEXT_1,
				[],
				false,
				DEFAULT_SENDER_FOR_TESTING,
			)
			o(initializedModel.getConversationType()).equals(ConversationType.NEW)
			o(initializedModel.getSubject()).equals(SUBJECT_LINE_1)
			o(initializedModel.getBody()).equals(BODY_TEXT_1)
			o(initializedModel.getDraft()).equals(null)
			o(initializedModel.allRecipients().length).equals(1)
			o(initializedModel.getSender()).equals(DEFAULT_SENDER_FOR_TESTING)
			o(model.isConfidential()).equals(true)
			o(model.containsExternalRecipients()).equals(false)
			o(initializedModel.getAttachments().length).equals(0)
			o(initializedModel.hasMailChanged()).equals(false)("initialization should not flag mail changed")
		})
		o("initWithTemplate duplicated recipients", async function () {
			const duplicate = {
				name: INTERNAL_RECIPIENT_1.name,
				address: INTERNAL_RECIPIENT_1.address,
				contact: INTERNAL_RECIPIENT_1.contact,
			}
			const initializedModel = await model.initWithTemplate(
				{
					to: [INTERNAL_RECIPIENT_1, duplicate],
				},
				SUBJECT_LINE_1,
				BODY_TEXT_1,
				[],
				false,
				DEFAULT_SENDER_FOR_TESTING,
			)
			o(initializedModel.getConversationType()).equals(ConversationType.NEW)
			o(initializedModel.getSubject()).equals(SUBJECT_LINE_1)
			o(initializedModel.getBody()).equals(BODY_TEXT_1)
			o(initializedModel.getDraft()).equals(null)
			o(initializedModel.allRecipients().length).equals(1)
			o(initializedModel.getSender()).equals(DEFAULT_SENDER_FOR_TESTING)
			o(model.isConfidential()).equals(true)
			o(model.containsExternalRecipients()).equals(false)
			o(initializedModel.getAttachments().length).equals(0)
			o(initializedModel.hasMailChanged()).equals(false)("initialization should not flag mail changed")
		})
		o("initWithDraft with blank data", async function () {
			const conversationEntryId = testIdGenerator.newIdTuple()
			const draft = createTestEntity(MailTypeRef, {
				confidential: false,
				sender: createTestEntity(MailAddressTypeRef),
				subject: "",
				conversationEntry: conversationEntryId,
			})
			const draftDetails = createTestEntity(MailDetailsTypeRef, {
				recipients: createTestEntity(RecipientsTypeRef),
				body: createTestEntity(BodyTypeRef, {
					text: BODY_TEXT_1,
				}),
			})
			const conversationEntry = createTestEntity(ConversationEntryTypeRef, {
				_id: conversationEntryId,
				mail: draft._id,
				conversationType: ConversationType.REPLY,
			})

			const initializedModel = await model.initWithDraft(draft, draftDetails, conversationEntry, [], new Map())
			o(initializedModel.getConversationType()).equals(ConversationType.REPLY)
			o(initializedModel.getSubject()).equals(draft.subject)
			o(initializedModel.getBody()).equals(BODY_TEXT_1)
			o(initializedModel.getDraft()).equals(draft)
			o(initializedModel.allRecipients().length).equals(0)
			o(initializedModel.getSender()).equals(DEFAULT_SENDER_FOR_TESTING)
			o(model.isConfidential()).equals(true)
			o(model.containsExternalRecipients()).equals(false)
			o(initializedModel.getAttachments().length).equals(0)
			o(initializedModel.hasMailChanged()).equals(false)("initialization should not flag mail changed")
		})
		o("initWithDraft with some data", async function () {
			const conversationEntryId = testIdGenerator.newIdTuple()
			const draft = createTestEntity(MailTypeRef, {
				confidential: true,
				sender: createTestEntity(MailAddressTypeRef),
				subject: SUBJECT_LINE_1,
				conversationEntry: conversationEntryId,
			})
			const conversationEntry = createTestEntity(ConversationEntryTypeRef, {
				_id: conversationEntryId,
				mail: draft._id,
				conversationType: ConversationType.FORWARD,
			})
			const recipients = createTestEntity(RecipientsTypeRef, {
				toRecipients: [
					createTestEntity(MailAddressTypeRef, {
						address: "",
					}),
					createTestEntity(MailAddressTypeRef, {
						address: EXTERNAL_ADDRESS_1,
					}),
				],
				ccRecipients: [
					createTestEntity(MailAddressTypeRef, {
						address: EXTERNAL_ADDRESS_2,
					}),
				],
			})
			const draftDetails = createTestEntity(MailDetailsTypeRef, {
				recipients,
				body: createTestEntity(BodyTypeRef, { text: BODY_TEXT_1 }),
			})

			const initializedModel = await model.initWithDraft(draft, draftDetails, conversationEntry, [], new Map())
			o(initializedModel.getConversationType()).equals(ConversationType.FORWARD)
			o(initializedModel.getSubject()).equals(draft.subject)
			o(initializedModel.getBody()).equals(BODY_TEXT_1)
			o(initializedModel.getDraft()).equals(draft)
			o(initializedModel.allRecipients().length).equals(2)("Only MailAddresses with a valid address will be accepted as recipients")
			o(initializedModel.toRecipients().length).equals(1)
			o(initializedModel.ccRecipients().length).equals(1)
			o(initializedModel.bccRecipients().length).equals(0)
			o(initializedModel.getSender()).equals(DEFAULT_SENDER_FOR_TESTING)
			o(model.isConfidential()).equals(true)
			o(model.containsExternalRecipients()).equals(true)
			o(initializedModel.getAttachments().length).equals(0)
		})
	})
	o.spec("Adding and removing recipients", function () {
		o.beforeEach(async function () {
			await model.initWithTemplate({}, "", "", [], false, "")
		})

		o("adding duplicate to-recipient", async function () {
			const recipient = {
				name: "sanchez",
				address: "123@test.com",
				contact: null,
				type: null,
			}
			model.addRecipient(RecipientField.TO, recipient)
			const r1 = model.getRecipient(RecipientField.TO, recipient.address)!

			model.addRecipient(RecipientField.TO, recipient)

			verify(recipientsModel.initialize(recipient), { times: 1 })

			o(model.toRecipients().length).equals(1)
			o(model.ccRecipients().length).equals(0)
			o(model.bccRecipients().length).equals(0)
		})
		o("add different to-recipients", async function () {
			const pablo = {
				name: "pablo",
				address: "pablo94@test.co.uk",
				contact: null,
				type: null,
			}
			const cortez = {
				name: "cortez",
				address: "c.asd@test.net",
				contact: null,
				type: null,
			}
			model.addRecipient(RecipientField.TO, pablo)
			model.addRecipient(RecipientField.TO, cortez)

			verify(recipientsModel.initialize(pablo))
			verify(recipientsModel.initialize(cortez))

			o(model.toRecipients().length).equals(2)
			o(model.ccRecipients().length).equals(0)
			o(model.bccRecipients().length).equals(0)
		})
		o("add duplicate recipients to different fields", async function () {
			const recipient = {
				name: "sanchez",
				address: "123@test.com",
				contact: null,
				type: null,
			}
			model.addRecipient(RecipientField.TO, recipient)
			model.addRecipient(RecipientField.CC, recipient)

			verify(recipientsModel.initialize(recipient), { times: 2 })

			o(model.toRecipients().length).equals(1)
			o(model.ccRecipients().length).equals(1)
			o(model.bccRecipients().length).equals(0)
		})
	})
	o.spec("Sending", function () {
		o("completely blank email", async function () {
			const method = MailMethod.NONE
			const getConfirmation = func<() => Promise<boolean>>()
			const e = await assertThrows(UserError, () => model.send(method, getConfirmation))
			o(e?.message).equals(lang.get("noRecipients_msg"))
			verify(getConfirmation(), { times: 0 })
			verify(mailFacade.sendDraft(anything(), anything(), anything()), { times: 0 })
			verify(mailFacade.createDraft(anything()), { times: 0 })
			verify(mailFacade.updateDraft(anything()), { times: 0 })
		})
		o("blank subject no confirm", async function () {
			model.addRecipient(RecipientField.TO, {
				name: "test",
				address: "test@address.com",
				contact: null,
			})

			const method = MailMethod.NONE
			const getConfirmation = func<() => Promise<boolean>>()
			const r = await model.send(method, getConfirmation)
			o(r).equals(false)
			verify(getConfirmation(), { times: 0 })
			verify(mailFacade.sendDraft(anything(), anything(), anything()), { times: 0 })
			verify(mailFacade.createDraft(anything()), { times: 0 })
			verify(mailFacade.updateDraft(anything()), { times: 0 })
		})
		o("confidential missing password", async function () {
			await model.addRecipient(RecipientField.TO, {
				name: "test",
				address: "test@address.com",
				contact: null,
			})
			model.setConfidential(true)
			const method = MailMethod.NONE

			const getConfirmation = func<(TranslationKey) => Promise<boolean>>()
			when(getConfirmation(anything())).thenResolve(true)

			const e = await assertThrows(UserError, () => model.send(method, getConfirmation))
			o(e?.message).equals(lang.get("noPreSharedPassword_msg"))

			verify(mailFacade.sendDraft(anything(), anything(), anything()), { times: 0 })
			verify(mailFacade.createDraft(anything()), { times: 0 })
			verify(mailFacade.updateDraft(anything()), { times: 0 })
		})
		o("confidential weak password no confirm", async function () {
			const recipient = {
				name: "test",
				address: "test@address.com",
				contact: null,
			}
			await model.initWithTemplate({ to: [recipient] }, "subject", "", [], true, "me@tuta.com", false)
			model.setPassword("test@address.com", "abc")
			o(model.getPassword(recipient.address)).equals("abc")
			const method = MailMethod.NONE

			const getConfirmation = func<(TranslationKey) => Promise<boolean>>()
			when(getConfirmation(anything())).thenResolve(false)
			const r = await model.send(method, getConfirmation)
			o(r).equals(false)
			verify(mailFacade.sendDraft(anything(), anything(), anything()), { times: 0 })
			verify(mailFacade.createDraft(anything()), { times: 0 })
			verify(mailFacade.updateDraft(anything()), { times: 0 })
		})
		o("confidential weak password confirm", async function () {
			const recipient = {
				name: "test",
				address: "test@address.com",
				contact: null,
			}
			await model.initWithTemplate({ to: [recipient] }, "did you get that thing i sent ya?", "", [], true, "me@tutanota.de", false)
			const password = WEAK_PASSWORD
			model.setPassword("test@address.com", password)
			o(model.getPassword(recipient.address)).equals(password)
			const method = MailMethod.NONE
			const getConfirmation = func<(TranslationKey) => Promise<boolean>>()
			when(getConfirmation(anything())).thenResolve(true)

			const r = await model.send(method, getConfirmation)
			o(r).equals(true)

			verify(mailFacade.sendDraft(anything(), anything(), anything()), { times: 1 })
			verify(mailFacade.createDraft(anything()), { times: 1 })
			verify(mailFacade.updateDraft(anything()), { times: 0 })

			const contact = model.getRecipientList(RecipientField.TO)[0].contact!
			o(contact.presharedPassword).equals(password)
		})

		o("correct password will be returned from getPassword after calling setPassword", function () {
			model.setPassword("address1", "password1")
			model.setPassword("address2", "password2")

			o(model.getPassword("address2")).equals("password2")
			o(model.getPassword("address1")).equals("password1")
		})

		o("confidential strong password", async function () {
			const address = "test@address.com"
			const recipient = {
				name: "test",
				address: address,
				contact: null,
			}
			await model.initWithTemplate({ to: [recipient] }, "subjecttttt", "", [], true, "me@tutanota.de", false)
			const password = STRONG_PASSWORD
			model.setPassword(address, password)
			const method = MailMethod.NONE

			const getConfirmation = func<(TranslationKey) => Promise<boolean>>()

			const r = await model.send(method, getConfirmation)
			o(r).equals(true)

			verify(getConfirmation(anything), { times: 0 })

			verify(mailFacade.sendDraft(anything(), anything(), anything()), { times: 1 })
			verify(mailFacade.createDraft(anything()), { times: 1 })
			verify(mailFacade.updateDraft(anything()), { times: 0 })

			const contact = model.getRecipientList(RecipientField.TO)[0].contact!
			o(contact.presharedPassword).equals(password)
		})

		o("when a recipient has an existing contact, and the saved password changes, then the contact will be updated", async function () {
			const getConfirmation = func<(TranslationKey) => Promise<boolean>>()

			const contact = createTestEntity(ContactTypeRef, {
				_id: testIdGenerator.newIdTuple(),
				firstName: "my",
				lastName: "chippie",
				presharedPassword: "weak password",
			})
			await model.initWithTemplate({ to: [] }, "did you get that thing i sent ya?", "no?", [], true, "me@tutanota.de", false)

			await model.addRecipient(RecipientField.TO, {
				name: "chippie",
				address: "chippie@cinco.net",
				contact,
			})

			model.setPassword("chippie@cinco.net", STRONG_PASSWORD)
			await model.send(MailMethod.NONE, getConfirmation)
			verify(entity.update(contact), { times: 1 })
		})
	})

	o.spec("Entity Event Updates", function () {
		let existingContact
		let recipients
		o.before(function () {
			existingContact = createTestEntity(ContactTypeRef, {
				_id: testIdGenerator.newIdTuple(),
				firstName: "james",
				lastName: "hetfield",
			})

			recipients = [
				{
					name: "paul gilbert",
					address: "paul@gmail.com",
					contact: null,
				},
				{
					name: "james hetfield",
					address: "james@tuta.com",
					contact: existingContact,
				},
			]
		})

		o("nonmatching event", async function () {
			await model.handleEntityEvent({
				typeRef: UserTypeRef,
				operation: OperationType.CREATE,
				instanceListId: null,
				instanceId: "",
				...noPatchesAndInstance,
				prefetchStatus: PrefetchStatus.NotPrefetched,
			})
			await model.handleEntityEvent({
				typeRef: CustomerTypeRef,
				operation: OperationType.CREATE,
				instanceListId: null,
				instanceId: "",
				...noPatchesAndInstance,
				prefetchStatus: PrefetchStatus.NotPrefetched,
			})
			await model.handleEntityEvent({
				typeRef: NotificationMailTypeRef,
				operation: OperationType.CREATE,
				instanceListId: null,
				instanceId: "",
				...noPatchesAndInstance,
				prefetchStatus: PrefetchStatus.NotPrefetched,
			})
			await model.handleEntityEvent({
				typeRef: ChallengeTypeRef,
				operation: OperationType.CREATE,
				instanceListId: null,
				instanceId: "",
				...noPatchesAndInstance,
				prefetchStatus: PrefetchStatus.NotPrefetched,
			})
			await model.handleEntityEvent({
				typeRef: MailTypeRef,
				operation: OperationType.CREATE,
				instanceListId: "mail-list-id",
				instanceId: "",
				...noPatchesAndInstance,
				prefetchStatus: PrefetchStatus.NotPrefetched,
			})
			verify(entity.load(anything(), anything(), anything()), { times: 0 })
		})

		o("contact updated email kept", async function () {
			const [instanceListId, instanceId] = existingContact._id
			const contactForUpdate = {
				firstName: "newfirstname",
				lastName: "newlastname",
				mailAddresses: [
					createTestEntity(MailAddressTypeRef, {
						address: "james@tuta.com",
					}),
					createTestEntity(MailAddressTypeRef, {
						address: "address2@hotmail.com",
					}),
				],
			}
			when(
				entity.load(
					ContactTypeRef,
					argThat((id) => isSameId(id, existingContact._id)),
				),
			).thenResolve(createContact(Object.assign({ _id: existingContact._id } as Contact, contactForUpdate)))
			await model.initWithTemplate({ to: recipients }, "somb", "", [], true, "a@b.c", false)
			await model.handleEntityEvent({
				typeRef: ContactTypeRef,
				operation: OperationType.UPDATE,
				instanceListId,
				instanceId,
				...noPatchesAndInstance,
				prefetchStatus: PrefetchStatus.NotPrefetched,
			})
			o(model.allRecipients().length).equals(2)
			const updatedRecipient = model.allRecipients().find((r) => r.contact && isSameId(r.contact._id, existingContact._id))
			o(updatedRecipient && updatedRecipient.name).equals(getContactDisplayName(downcast(contactForUpdate)))
		})
		o("contact updated email removed or changed", async function () {
			const [instanceListId, instanceId] = existingContact._id
			const contactForUpdate = {
				firstName: "james",
				lastName: "hetfield",
				mailAddresses: [
					createTestEntity(MailAddressTypeRef, {
						address: "nolongerjames@hotmail.com",
					}),
				],
			}

			when(entity.load(ContactTypeRef, existingContact._id)).thenResolve(
				createContact(
					Object.assign(
						{
							_id: existingContact._id,
						} as Contact,
						contactForUpdate,
					),
				),
			)
			await model.initWithTemplate({ to: recipients }, "b", "c", [], true, "", false)
			await model.handleEntityEvent({
				typeRef: ContactTypeRef,
				operation: OperationType.UPDATE,
				instanceListId,
				instanceId,
				...noPatchesAndInstance,
				prefetchStatus: PrefetchStatus.NotPrefetched,
			})
			o(model.allRecipients().length).equals(1)
			const updatedContact = model.allRecipients().find((r) => r.contact && isSameId(r.contact._id, existingContact._id))
			o(updatedContact ?? null).equals(null)
		})
		o("contact removed", async function () {
			const [instanceListId, instanceId] = existingContact._id
			await model.initWithTemplate({ to: recipients }, "subj", "", [], true, "a@b.c", false)
			await model.handleEntityEvent({
				typeRef: ContactTypeRef,
				operation: OperationType.DELETE,
				instanceListId,
				instanceId,
				...noPatchesAndInstance,
				prefetchStatus: PrefetchStatus.NotPrefetched,
			})
			o(model.allRecipients().length).equals(1)
			const updatedContact = model.allRecipients().find((r) => r.contact && isSameId(r.contact._id, existingContact._id))
			o(updatedContact == null).equals(true)
		})
		o("too many to recipients dont confirm", async function () {
			const recipients = {
				to: [] as { name: string; address: string }[],
			}

			for (let i = 0; i < TOO_MANY_VISIBLE_RECIPIENTS; ++i) {
				recipients.to.push({
					name: `person ${i}`,
					address: `person${i}@tutanota.de`,
				})
			}

			const subject = "subyekt"
			const body = "bodie"

			const getConfirmation = func<(key: TranslationKey) => Promise<boolean>>()
			when(getConfirmation("manyRecipients_msg")).thenResolve(false)

			await model.initWithTemplate(recipients, subject, body, [], false, "eggs@tutanota.de", false)
			const hasBeenSent = await model.send(MailMethod.NONE, getConfirmation)
			o(hasBeenSent).equals(false)("nothing was sent")
			verify(getConfirmation("manyRecipients_msg"), { times: 1 })
		})
		o("too many to recipients confirm", async function () {
			const recipients = {
				to: [] as { name: string; address: string }[],
			}

			for (let i = 0; i < TOO_MANY_VISIBLE_RECIPIENTS; ++i) {
				recipients.to.push({
					name: `person ${i}`,
					address: `person${i}@tutanota.de`,
				})
			}

			const subject = "subyekt"
			const body = "bodie"

			const getConfirmation = func<(key: TranslationKey) => Promise<boolean>>()
			when(getConfirmation("manyRecipients_msg")).thenResolve(true)

			await model.initWithTemplate(recipients, subject, body, [], false, "eggs@tutanota.de")

			o(await model.send(MailMethod.NONE, getConfirmation)).equals(true)
			verify(getConfirmation("manyRecipients_msg"), { times: 1 })
		})
		o("too many cc recipients dont confirm", async function () {
			const recipients = {
				cc: [] as { name: string; address: string }[],
			}

			for (let i = 0; i < TOO_MANY_VISIBLE_RECIPIENTS; ++i) {
				recipients.cc.push({
					name: `person ${i}`,
					address: `person${i}@tutanota.de`,
				})
			}

			const subject = "subyekt"
			const body = "bodie"

			const getConfirmation = func<(key: TranslationKey) => Promise<boolean>>()
			when(getConfirmation("manyRecipients_msg")).thenResolve(false)

			await model.initWithTemplate(recipients, subject, body, [], false, "eggs@tutanota.de")
			o(await model.send(MailMethod.NONE, getConfirmation)).equals(false)
			verify(getConfirmation("manyRecipients_msg"), { times: 1 })
		})
		o("too many cc recipients confirm", async function () {
			const recipients = {
				cc: [] as { name: string; address: string }[],
			}

			for (let i = 0; i < TOO_MANY_VISIBLE_RECIPIENTS; ++i) {
				recipients.cc.push({
					name: `person ${i}`,
					address: `person${i}@tutanota.de`,
				})
			}

			const subject = "subyekt"
			const body = "bodie"

			const getConfirmation = func<(key: TranslationKey) => Promise<boolean>>()
			when(getConfirmation("manyRecipients_msg")).thenResolve(true)

			await model.initWithTemplate(recipients, subject, body, [], false, "eggs@tutanota.de")
			o(await model.send(MailMethod.NONE, getConfirmation)).equals(true)
			verify(getConfirmation("manyRecipients_msg"), { times: 1 })
		})
		o.spec("mail draft update", () => {
			const draftListId = "some draft list id"
			const draftElementId = "some draft element id"

			o.beforeEach(() => {
				model.draft = createTestEntity(MailTypeRef, {
					mailDetailsDraft: [draftListId, draftElementId],
				})
			})

			o.spec("non matching", () => {
				o.test("different id", async () => {
					model._draftSavedRecently = false
					model.setMailSavedAt(1000)
					model.setMailRemotelyUpdatedAt(1000)
					now = 1234

					await model.handleEntityEvent({
						typeRef: MailDetailsDraftTypeRef,
						operation: OperationType.UPDATE,
						instanceListId: draftListId,
						instanceId: `not ${draftElementId}`,
						...noPatchesAndInstance,
						prefetchStatus: PrefetchStatus.NotPrefetched,
					})

					o.check(model.getMailRemotelyUpdatedAt()).equals(1000)
					o.check(model.hasDraftDataChangedOnServer()).equals(false)

					verify(db.setAutosavedDraftData(matchers.anything()), { times: 0 })
				})
				o.test("no draft", async () => {
					model.draft = null
					model._draftSavedRecently = false
					model.setMailSavedAt(0)
					model.setMailRemotelyUpdatedAt(0)
					now = 1234

					await model.handleEntityEvent({
						typeRef: MailDetailsDraftTypeRef,
						operation: OperationType.UPDATE,
						instanceListId: draftListId,
						instanceId: draftElementId,
						...noPatchesAndInstance,
						prefetchStatus: PrefetchStatus.NotPrefetched,
					})

					o.check(model.getMailRemotelyUpdatedAt()).equals(0)
					o.check(model.hasDraftDataChangedOnServer()).equals(false)

					verify(db.setAutosavedDraftData(matchers.anything()), { times: 0 })
				})
			})

			o.test("matching, recently saved", async () => {
				model._draftSavedRecently = true
				model.setMailSavedAt(1000)
				model.setMailRemotelyUpdatedAt(1000)
				now = 1234
				await model.handleEntityEvent({
					typeRef: MailDetailsDraftTypeRef,
					operation: OperationType.UPDATE,
					instanceListId: draftListId,
					instanceId: draftElementId,
					...noPatchesAndInstance,
					prefetchStatus: PrefetchStatus.NotPrefetched,
				})
				o.check(model._draftSavedRecently).equals(false)
				o.check(model.getMailRemotelyUpdatedAt()).equals(1000)
				o.check(model.hasDraftDataChangedOnServer()).equals(false)

				verify(db.setAutosavedDraftData(matchers.anything()), { times: 0 })
			})

			o.test("matching, not recently saved", async () => {
				model._draftSavedRecently = false
				model.setMailSavedAt(1000)
				model.setMailRemotelyUpdatedAt(1000)
				model.setBody("we changed the body")
				now = 1234
				await model.handleEntityEvent({
					typeRef: MailDetailsDraftTypeRef,
					operation: OperationType.UPDATE,
					instanceListId: draftListId,
					instanceId: draftElementId,
					...noPatchesAndInstance,
					prefetchStatus: PrefetchStatus.NotPrefetched,
				})
				o.check(model.getMailRemotelyUpdatedAt()).equals(1234)
				o.check(model.hasDraftDataChangedOnServer()).equals(true)

				verify(db.setAutosavedDraftData(matchers.anything()), { times: 1 })
			})
		})
	})
})
