// Package mail owns the immutable Mail Builtin Extension Definition and its
// adapter to the provider-neutral Mail service.
package mail

import (
	"github.com/tidbcloud/AgentTeam/internal/extension/providers/mail/protocol"
	"github.com/tidbcloud/AgentTeam/internal/extension/registry"
)

const (
	Domain   = "mail"
	Version  = 1
	Revision = "mail-p0-v1"
	// DefinitionDigest pins the Mail-owned App capability to this immutable
	// Definition and implementation revision.
	DefinitionDigest = "sha256:ad0daf463654be1d9c8a2a38473036eca4c508b8524998bcc90c73df0fdeaebe"

	ActionSearchMessages     = "search-messages"
	ActionGetMessage         = "get-message"
	ActionListAttachments    = "list-attachments"
	ActionGetAttachment      = "get-attachment"
	ActionCreateDraft        = "create-draft"
	ActionUpdateDraft        = "update-draft"
	ActionCreateReplyDraft   = "create-reply-draft"
	ActionCreateForwardDraft = "create-forward-draft"
	ActionSendDraft          = "send-draft"

	ScopeMetadataRead = "mail.metadata.read"
	ScopeContentRead  = "mail.content.read"
	ScopeDraftWrite   = "mail.draft.write"
	ScopeSend         = "mail.send"
)

// Definition returns the Mail P0 Action catalog. RequiredScopes is the single
// server-owned all-of scope mapping consumed by Catalog projection and
// invocation preflight.
func Definition() registry.Definition {
	return registry.Definition{
		Domain:      Domain,
		Version:     Version,
		Name:        "Email",
		Description: "Read, draft, and send email through one authorized mailbox.",
		Actions:     mailActions(),
	}
}

func actionSupportedByIMAPCapabilities(
	capabilities protocol.Capabilities,
	action string,
) bool {
	hasIMAP := capabilities.Has("IMAP4REV1") ||
		capabilities.Has("IMAP4REV2")
	switch action {
	case ActionSearchMessages, ActionGetMessage,
		ActionListAttachments, ActionGetAttachment:
		return hasIMAP
	case ActionCreateDraft:
		return hasIMAP &&
			capabilities.DraftMailbox != "" &&
			capabilities.Has("SPECIAL-USE") &&
			capabilities.Has("UIDPLUS")
	case ActionUpdateDraft, ActionCreateReplyDraft,
		ActionCreateForwardDraft, ActionSendDraft:
		return hasIMAP && capabilities.SafeDraftMutation()
	default:
		return false
	}
}

func createDraftInputSchema() map[string]any {
	return objectSchema(map[string]any{
		"clientRequestId": clientRequestIDSchema(),
		"to":              addressSetSchema(),
		"cc":              addressSetSchema(),
		"bcc":             addressSetSchema(),
		"subject":         map[string]any{"type": "string", "maxLength": 998},
		"body":            bodySchema(),
		"attachments":     draftAttachmentsSchema(),
	}, []any{"clientRequestId", "to", "subject", "body"})
}

func draftAttachmentsSchema() map[string]any {
	return map[string]any{
		"type":        "array",
		"maxItems":    10,
		"uniqueItems": true,
		"items": objectSchema(map[string]any{
			"attachmentId": map[string]any{
				"type":      "string",
				"minLength": 1,
				"maxLength": 128,
				"description": "AgentTeam attachment ID the invoking agent can already read; " +
					"paths and provider URLs are never accepted.",
			},
		}, []any{"attachmentId"}),
		"description": "AgentTeam attachments to bind to the draft, " +
			"bounded to 10 items, 10 MiB each and 20 MiB total.",
	}
}

func updateDraftInputSchema() map[string]any {
	schema := createDraftInputSchema()
	properties := schema["properties"].(map[string]any)
	properties["draft"] = resourceAliasSchema()
	properties["expectedRevision"] = map[string]any{"type": "integer", "minimum": 1}
	schema["required"] = []any{
		"clientRequestId", "draft", "expectedRevision", "to", "subject", "body",
	}
	return schema
}

func searchFiltersSchema() map[string]any {
	schema := objectSchema(map[string]any{
		"text": nonEmptyStringSchema(1024),
		"folder": map[string]any{
			"type": "string",
			"enum": []any{
				"inbox", "sent", "drafts", "archive", "trash", "spam", "all",
			},
		},
		"from":           searchAddressSetSchema(),
		"to":             searchAddressSetSchema(),
		"receivedAfter":  dateTimeSchema(),
		"receivedBefore": dateTimeSchema(),
		"unread":         map[string]any{"type": "boolean"},
		"hasAttachments": map[string]any{"type": "boolean"},
	}, nil)
	schema["minProperties"] = 1
	return schema
}

func searchOutputSchema() map[string]any {
	return objectSchema(map[string]any{
		"messages": map[string]any{
			"type":     "array",
			"maxItems": 50,
			"items":    messageMetadataSchema(),
		},
		"nextCursor": map[string]any{"type": "string", "maxLength": 2048},
		"hasMore":    map[string]any{"type": "boolean"},
		"truncated":  booleanSchema(),
	}, []any{"messages", "hasMore", "truncated"})
}

func messageMetadataSchema() map[string]any {
	return objectSchema(map[string]any{
		"message":                  resourceAliasSchema(),
		"attachmentMessage":        resourceAliasSchema(),
		"replyMessage":             resourceAliasSchema(),
		"forwardMessage":           resourceAliasSchema(),
		"from":                     nonEmptyStringSchema(320),
		"to":                       addressSetSchema(),
		"subject":                  map[string]any{"type": "string", "maxLength": 998},
		"sentAt":                   dateTimeSchema(),
		"sizeBytes":                map[string]any{"type": "integer", "minimum": 0},
		"unread":                   booleanSchema(),
		"hasAttachments":           booleanSchema(),
		"truncated":                booleanSchema(),
		"untrustedExternalContent": untrustedExternalContentSchema(),
	}, []any{
		"message", "attachmentMessage", "replyMessage", "forwardMessage",
		"from", "to", "subject", "sentAt", "sizeBytes", "unread",
		"hasAttachments", "truncated", "untrustedExternalContent",
	})
}

func messageOutputSchema() map[string]any {
	properties := messageMetadataSchema()["properties"].(map[string]any)
	properties["cc"] = addressSetSchema()
	properties["body"] = readBodySchema()
	return objectSchema(properties, []any{
		"message", "attachmentMessage", "replyMessage", "forwardMessage",
		"from", "to", "subject", "sentAt", "body", "hasAttachments",
		"truncated", "untrustedExternalContent",
	})
}

func attachmentMetadataSchema() map[string]any {
	return objectSchema(map[string]any{
		"attachment":               resourceAliasSchema(),
		"filename":                 nonEmptyStringSchema(1024),
		"mediaType":                nonEmptyStringSchema(255),
		"sizeBytes":                map[string]any{"type": "integer", "minimum": 0},
		"untrustedExternalContent": untrustedExternalContentSchema(),
	}, []any{
		"attachment", "filename", "mediaType", "sizeBytes",
		"untrustedExternalContent",
	})
}

func readBodySchema() map[string]any {
	return objectSchema(map[string]any{
		"format": map[string]any{
			"type": "string",
			"enum": []any{"text", "sanitized_html"},
		},
		"content":                  map[string]any{"type": "string", "maxLength": 524288},
		"truncated":                booleanSchema(),
		"untrustedExternalContent": untrustedExternalContentSchema(),
	}, []any{
		"format", "content", "truncated", "untrustedExternalContent",
	})
}

func untrustedExternalContentSchema() map[string]any {
	return map[string]any{"type": "boolean", "const": true}
}

// getAttachmentOutputSchema is a conditional contract: a materialized result
// must carry the full projection, while the conservative unknown result must
// not carry any attachment identity that could invite a duplicate retry.
func getAttachmentOutputSchema() map[string]any {
	return map[string]any{
		"$schema": "https://json-schema.org/draft/2020-12/schema",
		"oneOf": []any{
			objectSchema(map[string]any{
				"status":                   map[string]any{"type": "string", "const": "materialized"},
				"attachmentId":             nonEmptyStringSchema(128),
				"filename":                 nonEmptyStringSchema(1024),
				"mediaType":                nonEmptyStringSchema(255),
				"sizeBytes":                map[string]any{"type": "integer", "minimum": 1},
				"truncated":                booleanSchema(),
				"untrustedExternalContent": untrustedExternalContentSchema(),
			}, []any{
				"status", "attachmentId", "filename", "mediaType",
				"sizeBytes", "truncated", "untrustedExternalContent",
			}),
			objectSchema(map[string]any{
				"status":                   map[string]any{"type": "string", "const": "unknown"},
				"truncated":                booleanSchema(),
				"untrustedExternalContent": untrustedExternalContentSchema(),
			}, []any{"status", "truncated", "untrustedExternalContent"}),
		},
	}
}

func draftOutputSchema() map[string]any {
	return objectSchema(map[string]any{
		"status": map[string]any{
			"type": "string",
			"enum": []any{"accepted", "failed", "unknown"},
		},
		"draft":     resourceAliasSchema(),
		"sendDraft": resourceAliasSchema(),
		"revision":  map[string]any{"type": "integer", "minimum": 1},
		"updatedAt": dateTimeSchema(),
		"truncated": booleanSchema(),
	}, []any{"status", "updatedAt", "truncated"})
}

func bodySchema() map[string]any {
	return objectSchema(map[string]any{
		"format":  map[string]any{"type": "string", "enum": []any{"text", "html"}},
		"content": map[string]any{"type": "string", "maxLength": 524288},
	}, []any{"format", "content"})
}

func addressSetSchema() map[string]any {
	return map[string]any{
		"type":        "array",
		"maxItems":    50,
		"uniqueItems": true,
		"items": map[string]any{
			"type":      "string",
			"minLength": 3,
			"maxLength": 320,
			"format":    "email",
		},
	}
}

func searchAddressSetSchema() map[string]any {
	schema := addressSetSchema()
	schema["maxItems"] = 10
	return schema
}

func clientRequestIDSchema() map[string]any {
	schema := nonEmptyStringSchema(128)
	schema["description"] = "Audit correlation only; not a durable deduplication key. After an unknown mutation result, reread the mailbox before issuing another command and never retry automatically."
	return schema
}

func resourceAliasSchema() map[string]any {
	return map[string]any{
		"type":      "string",
		"minLength": 1,
		"maxLength": 2048,
	}
}

func dateTimeSchema() map[string]any {
	return map[string]any{"type": "string", "format": "date-time"}
}

func booleanSchema() map[string]any {
	return map[string]any{"type": "boolean"}
}

func nonEmptyStringSchema(maxLength int) map[string]any {
	return map[string]any{
		"type":      "string",
		"minLength": 1,
		"maxLength": maxLength,
	}
}

func objectSchema(properties map[string]any, required []any) map[string]any {
	schema := map[string]any{
		"$schema":              "https://json-schema.org/draft/2020-12/schema",
		"type":                 "object",
		"additionalProperties": false,
		"properties":           properties,
	}
	if len(required) > 0 {
		schema["required"] = required
	}
	return schema
}
