package mail

import (
	"context"
	"encoding/json"
	"fmt"
	"reflect"

	"github.com/tidbcloud/AgentTeam/internal/extension/providers/mail/mailruntime"
	"github.com/tidbcloud/AgentTeam/internal/extension/registry"
)

// Executor is Mail's thin Builtin adapter to the provider-neutral service.
type Executor struct {
	service mailruntime.Invoker
}

// NewExecutor binds the immutable Definition to the Mail service.
func NewExecutor(service mailruntime.Invoker) *Executor {
	return &Executor{service: service}
}

// Strategy identifies this as an in-process native executor.
func (*Executor) Strategy() string { return "native" }

// Invoke preserves every server-resolved identity and never accepts provider,
// Installation, Connection, or credential identity from Action input.
func (executor *Executor) Invoke(
	ctx context.Context,
	invocation registry.BuiltinInvocation,
) (registry.BuiltinResult, error) {
	if executor == nil || isNilInvoker(executor.service) {
		return registry.BuiltinResult{},
			fmt.Errorf("mail executor is not configured")
	}
	result, err := executor.service.Invoke(ctx, mailruntime.Invocation{
		WorkspaceID:           invocation.WorkspaceID,
		AgentID:               invocation.AgentID,
		AgentMemberID:         invocation.AgentMemberID,
		ServiceInstallationID: invocation.ServiceInstallationID,
		ServiceConnectionID:   invocation.ServiceConnectionID,
		ServiceConnectionGeneration: invocation.
			ServiceConnectionGeneration,
		ServiceInstallationExtensionID: invocation.
			ServiceInstallationExtensionID,
		AuthorizedByMemberID: invocation.AuthorizedByMemberID,
		ExtensionName:        invocation.ExtensionName,
		Action:               invocation.Action,
		DefinitionVersion:    invocation.DefinitionVersion,
		DefinitionDigest:     invocation.DefinitionDigest,
		CatalogVersion:       invocation.CatalogVersion,
		RequestHash:          invocation.RequestHash,
		Input:                append([]byte(nil), invocation.Input...),
	})
	if err != nil {
		return registry.BuiltinResult{}, err
	}
	statusClass, err := builtinStatusClass(invocation.Action, result)
	if err != nil {
		return registry.BuiltinResult{}, err
	}
	auditFailureData, err := auditFailureFallback(
		invocation.Action,
		result,
	)
	if err != nil {
		return registry.BuiltinResult{}, err
	}
	return registry.BuiltinResult{
		Data:                 append([]byte(nil), result...),
		StatusClass:          statusClass,
		ResultClassification: statusClass,
		AuditFailureData:     auditFailureData,
	}, nil
}

func auditFailureFallback(
	action string,
	result json.RawMessage,
) (json.RawMessage, error) {
	switch action {
	case ActionCreateDraft,
		ActionUpdateDraft,
		ActionCreateReplyDraft,
		ActionCreateForwardDraft:
		var outcome struct {
			Status    string `json:"status"`
			UpdatedAt string `json:"updatedAt"`
			Truncated bool   `json:"truncated"`
		}
		if err := json.Unmarshal(result, &outcome); err != nil {
			return nil, fmt.Errorf(
				"prepare Mail mutation audit-failure result: %w",
				err,
			)
		}
		switch outcome.Status {
		case "accepted", "unknown":
			outcome.Status = "unknown"
			encoded, err := json.Marshal(outcome)
			if err != nil {
				return nil, fmt.Errorf(
					"encode Mail mutation audit-failure result: %w",
					err,
				)
			}
			return encoded, nil
		case "failed":
			return nil, nil
		default:
			return nil, fmt.Errorf(
				"prepare Mail mutation audit-failure result: invalid status %q",
				outcome.Status,
			)
		}
	case ActionSendDraft:
		var outcome struct {
			Status         string `json:"status"`
			RecipientCount int    `json:"recipientCount"`
			Truncated      bool   `json:"truncated"`
		}
		if err := json.Unmarshal(result, &outcome); err != nil {
			return nil, fmt.Errorf(
				"prepare Mail send audit-failure result: %w",
				err,
			)
		}
		switch outcome.Status {
		case "accepted", "unknown":
			outcome.Status = "unknown"
			encoded, err := json.Marshal(outcome)
			if err != nil {
				return nil, fmt.Errorf(
					"encode Mail send audit-failure result: %w",
					err,
				)
			}
			return encoded, nil
		case "failed":
			return nil, nil
		default:
			return nil, fmt.Errorf(
				"prepare Mail send audit-failure result: invalid status %q",
				outcome.Status,
			)
		}
	case ActionGetAttachment:
		var outcome struct {
			Status string `json:"status"`
		}
		if err := json.Unmarshal(result, &outcome); err != nil {
			return nil, fmt.Errorf(
				"prepare Mail attachment audit-failure result: %w",
				err,
			)
		}
		switch outcome.Status {
		case "materialized", "unknown":
			// The AgentTeam attachment may already exist; the fallback must not
			// leak its ID or invite another materialization attempt, even if
			// the upstream unknown result carried stray fields.
			return json.Marshal(struct {
				Status                   string `json:"status"`
				Truncated                bool   `json:"truncated"`
				UntrustedExternalContent bool   `json:"untrustedExternalContent"`
			}{
				Status:                   "unknown",
				Truncated:                false,
				UntrustedExternalContent: true,
			})
		default:
			return nil, fmt.Errorf(
				"prepare Mail attachment audit-failure result: invalid status %q",
				outcome.Status,
			)
		}
	default:
		return nil, nil
	}
}

func builtinStatusClass(
	action string,
	result json.RawMessage,
) (string, error) {
	switch action {
	case ActionCreateDraft,
		ActionUpdateDraft,
		ActionCreateReplyDraft,
		ActionCreateForwardDraft,
		ActionSendDraft:
		var outcome struct {
			Status string `json:"status"`
		}
		if err := json.Unmarshal(result, &outcome); err != nil {
			return "", fmt.Errorf(
				"classify Mail mutation result: %w",
				err,
			)
		}
		switch outcome.Status {
		case "accepted":
			return "success", nil
		case "failed", "unknown":
			return outcome.Status, nil
		default:
			return "", fmt.Errorf(
				"classify Mail mutation result: invalid status %q",
				outcome.Status,
			)
		}
	case ActionGetAttachment:
		var outcome struct {
			Status string `json:"status"`
		}
		if err := json.Unmarshal(result, &outcome); err != nil {
			return "", fmt.Errorf(
				"classify Mail attachment result: %w",
				err,
			)
		}
		switch outcome.Status {
		case "materialized":
			return "success", nil
		case "unknown":
			return "unknown", nil
		default:
			return "", fmt.Errorf(
				"classify Mail attachment result: invalid status %q",
				outcome.Status,
			)
		}
	default:
		return "success", nil
	}
}

func isNilInvoker(invoker mailruntime.Invoker) bool {
	if invoker == nil {
		return true
	}
	value := reflect.ValueOf(invoker)
	switch value.Kind() {
	case reflect.Chan, reflect.Func, reflect.Interface, reflect.Map,
		reflect.Pointer, reflect.Slice:
		return value.IsNil()
	default:
		return false
	}
}

// ActionNames enumerates the exact Action set the Mail executor dispatches.
// Bootstrap cross-checks it against the Definition catalog so a
// Definition/executor drift fails startup instead of surfacing as a runtime
// not-found.
func (*Executor) ActionNames() []string {
	return []string{
		ActionSearchMessages,
		ActionGetMessage,
		ActionListAttachments,
		ActionGetAttachment,
		ActionCreateDraft,
		ActionUpdateDraft,
		ActionCreateReplyDraft,
		ActionCreateForwardDraft,
		ActionSendDraft,
	}
}
