package mail

import (
	"bytes"
	"encoding/json"
	"io"
	"regexp"
	"strings"

	"github.com/tidbcloud/AgentTeam/internal/extension/providers/mail/connection"
	"github.com/tidbcloud/AgentTeam/internal/extension/providers/mail/protocol"
	"github.com/tidbcloud/AgentTeam/internal/extension/registry"
	appregistry "github.com/tidbcloud/AgentTeam/internal/store/app/registry"
	"github.com/tidbcloud/AgentTeam/internal/store/serviceactor"
)

// catalogPolicy is the Mail provider catalog facet. It re-imposes the Mail
// product contract the generic App projection cannot know: the App requires
// an explicit per-principal authorization (never the App default-allow
// mode), exactly one active connection whose subject is a Mail HMAC
// identity with a live credential generation, and a server-verified IMAP
// capability snapshot — then filters the scope-derived action set against
// that snapshot so the Agent catalog never advertises actions the current
// mailbox connection cannot serve.
type catalogPolicy struct{}

// CatalogPolicy returns the provider facet registered with the Mail
// Extension composite.
func CatalogPolicy() registry.CatalogPolicy { return catalogPolicy{} }

// mailSubjectRefPattern is the Mail h1 connection identity: an HMAC-SHA256
// over the canonical sender address, hex-encoded to 64 characters.
var mailSubjectRefPattern = regexp.MustCompile(
	`^email:h1:[0-9a-f]{64}$`,
)

func (catalogPolicy) FilterActions(
	input registry.CatalogPolicyInput,
) ([]string, bool, error) {
	if input.WorkspaceID == "" ||
		input.ServiceDefinitionID != appregistry.EmailAppID ||
		input.DefinitionDomain != Domain {
		return nil, false, nil
	}
	// The Mail App is explicit-only by product contract: a durable
	// capability rewritten to default-allow must not grant anything.
	if !input.ExplicitAuthorization {
		return nil, false, nil
	}
	if len(input.Connections) != 1 {
		return nil, false, nil
	}
	// Authoritative-unavailable states (wrong subject identity, missing
	// credential generation, inconsistent binding, or a config that fails
	// the capability verification) converge to unavailable with no error:
	// an error would make the reconciler retry forever and keep advertising
	// the stale catalog, which must instead disappear.
	connection := input.Connections[0]
	if !catalogConnectionAuthoritative(input, connection) {
		return nil, false, nil
	}
	capabilities, valid := decodeCatalogCapabilities(connection.Config)
	if !valid {
		return nil, false, nil
	}
	filtered := make([]string, 0, len(input.Actions))
	for _, action := range input.Actions {
		if catalogSupportsAction(capabilities, action) {
			filtered = append(filtered, action)
		}
	}
	if len(filtered) == 0 {
		return nil, false, nil
	}
	return filtered, true, nil
}

// catalogConnectionAuthoritative enforces the Mail connection contract on
// the secret-free connection identity: one active App connection bound to
// this workspace, installation, and definition, carrying a Mail HMAC
// subject identity and a non-empty credential-generation digest.
func catalogConnectionAuthoritative(
	input registry.CatalogPolicyInput,
	connection registry.CatalogConnection,
) bool {
	if connection.ID == "" ||
		connection.WorkspaceID != input.WorkspaceID ||
		connection.ServiceDefinitionID != appregistry.EmailAppID ||
		connection.ServiceInstallationID != input.ServiceInstallationID ||
		connection.ServiceKind != serviceactor.KindApp ||
		connection.Status != serviceactor.StatusActive {
		return false
	}
	if !mailSubjectRefPattern.MatchString(connection.ExternalSubjectRef) {
		return false
	}
	return connection.CurrentCredentialDigest != ""
}

func decodeCatalogCapabilities(
	payload []byte,
) (protocol.Capabilities, bool) {
	decoder := json.NewDecoder(bytes.NewReader(payload))
	decoder.DisallowUnknownFields()
	var config connection.Config
	if err := decoder.Decode(&config); err != nil {
		return protocol.Capabilities{}, false
	}
	if err := decoder.Decode(&struct{}{}); err != io.EOF {
		return protocol.Capabilities{}, false
	}
	if config.SchemaVersion != 1 ||
		!config.SenderAddressAccepted ||
		config.SenderAddress == "" ||
		config.CapabilityDigest == "" ||
		config.IMAP.Host == "" ||
		config.SMTP.Host == "" {
		return protocol.Capabilities{}, false
	}
	if err := config.VerifyDigest(); err != nil {
		return protocol.Capabilities{}, false
	}
	values := make(
		map[string]bool,
		len(config.IMAP.Capabilities),
	)
	previous := ""
	for _, capability := range config.IMAP.Capabilities {
		if capability == "" ||
			capability != strings.ToUpper(capability) ||
			(previous != "" && previous >= capability) {
			return protocol.Capabilities{}, false
		}
		previous = capability
		values[capability] = true
	}
	return protocol.Capabilities{
		Values:       values,
		DraftMailbox: config.IMAP.DraftMailbox,
	}, true
}

func catalogSupportsAction(
	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
	}
}
