class_name NetworkMailProtocol
extends RefCounted

const CAPABILITY: StringName = &"mail_v1"
const RELIABLE_CHANNEL: int = NetworkProtocol.MAIL_RELIABLE_CHANNEL
const MAX_ID_LENGTH: int = 96
const MAX_BODY_CHARACTERS: int = 2000
const MAX_BODY_BYTES: int = 8000
const MAX_RECIPIENT_LETTERS: int = 100
const MAX_SESSION_LETTERS: int = 200

const GREETINGS: PackedStringArray = ["dear", "to", "hey", "greetings"]
const SALUTATIONS: PackedStringArray = [
	"love", "from", "cheers", "salutations", "good_luck_have_fun",
]

enum State {
	SENT_UNREAD,
	READ,
	ACCEPTANCE_PENDING,
	ACCEPTED,
	DECLINED,
	ATTACHMENT_RECALLED,
	CANCELLED,
	SESSION_ENDED,
}


static func sanitize_body(value: Variant) -> String:
	if typeof(value) != TYPE_STRING:
		return ""
	var body := str(value).strip_edges(false, true)
	if (
		body.strip_edges().is_empty()
		or body.length() > MAX_BODY_CHARACTERS
		or body.to_utf8_buffer().size() > MAX_BODY_BYTES
	):
		return ""
	for index: int in body.length():
		var codepoint := body.unicode_at(index)
		if codepoint < 32 and codepoint not in [9, 10, 13]:
			return ""
		if codepoint == 127:
			return ""
	return body.replace("\r\n", "\n").replace("\r", "\n").replace("\t", "    ")


static func valid_id(value: Variant) -> bool:
	return (
		typeof(value) == TYPE_STRING
		and not str(value).is_empty()
		and str(value).length() <= MAX_ID_LENGTH
	)


static func validate_send_request(data: Variant) -> bool:
	if typeof(data) != TYPE_DICTIONARY:
		return false
	var value: Dictionary = data
	return (
		valid_id(value.get("request_id"))
		and valid_id(value.get("session_id"))
		and typeof(value.get("recipient_peer_id")) == TYPE_INT
		and int(value["recipient_peer_id"]) > 0
		and typeof(value.get("greeting_id")) == TYPE_STRING
		and str(value["greeting_id"]) in GREETINGS
		and not sanitize_body(value.get("body")).is_empty()
		and typeof(value.get("salutation_id")) == TYPE_STRING
		and str(value["salutation_id"]) in SALUTATIONS
		and typeof(value.get("reservation_id")) == TYPE_STRING
		and str(value["reservation_id"]).length() <= MAX_ID_LENGTH
		and typeof(value.get("attachment")) == TYPE_DICTIONARY
		and Dictionary(value["attachment"]).size() <= 3
		and NetworkIdentityCrypto.valid_fingerprint(
			value.get("sender_fingerprint")
		)
		and NetworkIdentityCrypto.valid_fingerprint(
			value.get("recipient_fingerprint")
		)
		and typeof(value.get("sender_signature")) == TYPE_PACKED_BYTE_ARRAY
	)


static func validate_mail(data: Variant) -> bool:
	if typeof(data) != TYPE_DICTIONARY:
		return false
	var value: Dictionary = data
	return (
		valid_id(value.get("mail_id"))
		and valid_id(value.get("session_id"))
		and typeof(value.get("sequence")) == TYPE_INT
		and typeof(value.get("sender_peer_id")) == TYPE_INT
		and typeof(value.get("recipient_peer_id")) == TYPE_INT
		and typeof(value.get("sender_display_name")) == TYPE_STRING
		and str(value["sender_display_name"]).length() <= 24
		and str(value.get("greeting_id", "")) in GREETINGS
		and not sanitize_body(value.get("body")).is_empty()
		and str(value.get("salutation_id", "")) in SALUTATIONS
		and typeof(value.get("attachment")) == TYPE_DICTIONARY
		and Dictionary(value["attachment"]).size() <= 3
		and typeof(value.get("state")) == TYPE_INT
		and int(value["state"]) >= 0
		and int(value["state"]) < State.size()
		and NetworkIdentityCrypto.valid_fingerprint(
			value.get("sender_fingerprint")
		)
		and NetworkIdentityCrypto.valid_fingerprint(
			value.get("recipient_fingerprint")
		)
		and typeof(value.get("sender_signature")) == TYPE_PACKED_BYTE_ARRAY
		and typeof(value.get("sender_public_key")) == TYPE_STRING
		and str(value["sender_public_key"]).to_utf8_buffer().size()
			<= NetworkIdentityCrypto.MAX_PUBLIC_KEY_BYTES
	)


static func mail_signature_fields(data: Dictionary) -> Array:
	var result: Array = [
		str(data.get("session_id", "")),
		str(data.get("request_id", "")),
		str(data.get("sender_fingerprint", "")),
		str(data.get("recipient_fingerprint", "")),
		str(data.get("greeting_id", "")),
		sanitize_body(data.get("body")),
		str(data.get("salutation_id", "")),
		str(data.get("reservation_id", "")),
	]
	result.append_array(attachment_signature_fields(data.get("attachment", {})))
	return result


static func attachment_signature_fields(value: Variant) -> Array:
	if typeof(value) != TYPE_DICTIONARY:
		return [0]
	var attachment: Dictionary = value
	var type := int(attachment.get("type", 0))
	var result: Array = [type]
	match type:
		1:
			result.append(int(attachment.get("amount", 0)))
		2:
			var fish: Dictionary = attachment.get("catch", {})
			result.append(str(attachment.get("catch_id", "")))
			result.append(str(fish.get("fish_id", "")))
			result.append(str(fish.get("weight_lb", "")))
			result.append(str(fish.get("display_scale", "")))
			result.append(int(fish.get("quality", -1)))
			result.append(int(fish.get("sale_value", 0)))
			result.append(bool(fish.get("is_favorited", false)))
		3:
			result.append(str(attachment.get("item_id", "")))
			result.append(int(attachment.get("quantity", 0)))
	return result


static func acceptance_signature_fields(
	data: Dictionary, sender_fingerprint: String, recipient_fingerprint: String
) -> Array:
	return [
		str(data.get("session_id", "")),
		str(data.get("request_id", "")),
		str(data.get("mail_id", "")),
		sender_fingerprint,
		recipient_fingerprint,
	]
