package dialectsfcrm

import (
	"context"
	"crypto/sha256"
	"encoding/json"
	"errors"
	"fmt"
	"net/mail"
	"strings"
	"time"
	"unicode/utf8"

	"github.com/nexuscrm/shared/pkg/contract"
	"github.com/nexuscrm/shared/pkg/models"
	connectordelivery "github.com/pingcap-inc/nexuscrm-connectors/features/connector/delivery"
)

const (
	SendRecordEmailCustomPermission        = "SendRecordEmail"
	SendRecordEmailSubjectMaxLength        = 255
	sendRecordEmailStandardActionName      = "sendRecordEmail"
	sendRecordEmailLegacyCompletionVersion = 1
	sendRecordEmailCompletionVersion       = 2
	sendRecordEmailCompletionTimeout       = 5 * time.Second
	sendRecordEmailRecipientEmailField     = "Email"
	sendRecordEmailRecipientOptOutField    = "HasOptedOutOfEmail"
	sendRecordEmailTaskSubject             = "Email"
)

type ConnectedMailboxIdentity struct {
	ConnectorKey     string
	PrincipalAddress string
	Generation       int64
}

type ConnectedMailboxIdentityReader interface {
	ConnectedMailboxIdentity(context.Context, string, string) (ConnectedMailboxIdentity, error)
}

// TrustedRecordFieldReader enforces object and record read access while
// reading only the app-selected fields needed for a server-side policy decision.
type TrustedRecordFieldReader interface {
	ReadTrustedRecordFields(context.Context, string, string, []string, *models.UserSession) (contract.Record, error)
}

type SendRecordEmailPermissionPolicy interface {
	CanSendRecordEmail(context.Context, *models.UserSession) bool
}

type RecordCreatePermissionPolicy interface {
	CanCreateRecord(context.Context, string, []string, *models.UserSession) bool
}

type ConnectedEmailDelivery interface {
	Lookup(context.Context, string, string) (connectordelivery.Result, error)
	Dispatch(context.Context, connectordelivery.Request) (connectordelivery.Result, error)
	Complete(context.Context, string, string) (connectordelivery.Result, error)
}

type ImmutableRecord struct {
	ObjectAPIName string
	RecordID      string
	Fields        contract.Record
}

// ImmutableRecordWriter atomically creates missing records and verifies that
// records already present under the stable IDs have the same field values.
type ImmutableRecordWriter interface {
	EnsureImmutableRecords(context.Context, []ImmutableRecord, *models.UserSession) error
}

type SendRecordEmailDeps struct {
	Records         TrustedRecordFieldReader
	Permission      SendRecordEmailPermissionPolicy
	RecordCreate    RecordCreatePermissionPolicy
	MailboxIdentity ConnectedMailboxIdentityReader
	Delivery        ConnectedEmailDelivery
	RecordWriter    ImmutableRecordWriter
}

type SendRecordEmailInput struct {
	RequestID     string
	RecordID      string
	ObjectAPIName string
	Subject       string
	Body          string
}

type SendRecordEmailResult struct {
	TaskID            string
	EmailMessageID    string
	ProviderMessageID string
	ProviderThreadID  string
}

type AcceptedEmailCompletion struct {
	Version        int    `json:"version"`
	RequestID      string `json:"requestId"`
	TaskID         string `json:"taskId"`
	EmailMessageID string `json:"emailMessageId"`
	UserID         string `json:"userId"`
	ObjectAPIName  string `json:"objectApiName"`
	RecordID       string `json:"recordId"`
	FromAddress    string `json:"fromAddress"`
	ToAddress      string `json:"toAddress"`
	Subject        string `json:"subject"`
	TextBody       string `json:"textBody"`
}

func ExecuteSendRecordEmailAction(ctx context.Context, deps SendRecordEmailDeps, input SendRecordEmailInput, user *models.UserSession) (*SendRecordEmailResult, error) {
	if deps.Records == nil || deps.Permission == nil || deps.RecordCreate == nil || deps.MailboxIdentity == nil ||
		deps.Delivery == nil || deps.RecordWriter == nil || user == nil || strings.TrimSpace(user.ID) == "" {
		return nil, fmt.Errorf("send record email is unavailable")
	}
	input.RequestID = strings.TrimSpace(input.RequestID)
	input.RecordID = strings.TrimSpace(input.RecordID)
	input.ObjectAPIName = strings.TrimSpace(input.ObjectAPIName)
	input.Subject = strings.TrimSpace(input.Subject)
	userID := strings.TrimSpace(user.ID)
	if input.RequestID == "" || len(input.RequestID) > 255 {
		return nil, fmt.Errorf("send record email requires a stable request ID")
	}
	existing, err := deps.Delivery.Lookup(ctx, input.RequestID, userID)
	if err == nil {
		return finishAcceptedEmail(ctx, deps, existing, input, userID, user)
	}
	if !errors.Is(err, connectordelivery.ErrDeliveryNotFound) {
		return nil, fmt.Errorf("look up connected Gmail delivery: %w", err)
	}

	if input.RecordID == "" || (input.ObjectAPIName != "Contact" && input.ObjectAPIName != "Lead") {
		return nil, fmt.Errorf("send record email requires a Contact or Lead record")
	}
	if input.Subject == "" || strings.ContainsAny(input.Subject, "\r\n") || utf8.RuneCountInString(input.Subject) > SendRecordEmailSubjectMaxLength {
		return nil, fmt.Errorf("send record email requires a valid subject")
	}
	if strings.TrimSpace(input.Body) == "" || len(input.Body) > 48*1024 {
		return nil, fmt.Errorf("send record email requires a plain-text body of at most 48 KiB")
	}
	if !deps.Permission.CanSendRecordEmail(ctx, user) {
		return nil, fmt.Errorf("permission denied: %s is required", SendRecordEmailCustomPermission)
	}
	for _, objectAPIName := range []string{"Task", "EmailMessage"} {
		fields := SendRecordEmailActivityCreateFields(objectAPIName)
		if !deps.RecordCreate.CanCreateRecord(ctx, objectAPIName, fields, user) {
			return nil, fmt.Errorf("permission denied: %s.Create and required field create access are required to send record email", objectAPIName)
		}
	}

	recipient, err := deps.Records.ReadTrustedRecordFields(ctx, input.ObjectAPIName, input.RecordID,
		[]string{"Id", sendRecordEmailRecipientEmailField, sendRecordEmailRecipientOptOutField}, user)
	if err != nil {
		return nil, fmt.Errorf("load email recipient record: %w", err)
	}
	if strings.TrimSpace(sendRecordEmailString(recipient["Id"])) != input.RecordID {
		return nil, fmt.Errorf("email recipient record does not match the requested record")
	}
	// Salesforce checkbox semantics opt a recipient out only on an explicit true.
	// Preserve that behavior for older records whose field is explicitly NULL,
	// while keeping omitted or malformed values fail-closed: omission can mean
	// that the field reader did not authorize or supply the requested field.
	rawOptedOut, exists := recipient[sendRecordEmailRecipientOptOutField]
	if !exists {
		return nil, fmt.Errorf("email recipient opt-out state is unavailable")
	}
	optedOut := false
	if rawOptedOut != nil {
		var ok bool
		optedOut, ok = rawOptedOut.(bool)
		if !ok {
			return nil, fmt.Errorf("email recipient opt-out state is unavailable")
		}
	}
	if optedOut {
		return nil, fmt.Errorf("recipient has opted out of email")
	}
	recipientAddress := strings.TrimSpace(sendRecordEmailString(recipient[sendRecordEmailRecipientEmailField]))
	if !isBareEmailAddress(recipientAddress) {
		return nil, fmt.Errorf("recipient record does not have a valid email address")
	}

	identity, err := deps.MailboxIdentity.ConnectedMailboxIdentity(ctx, userID, GmailConnectedMailboxKey)
	if err != nil {
		return nil, fmt.Errorf("resolve connected Gmail account: %w", err)
	}
	identity.PrincipalAddress = strings.TrimSpace(identity.PrincipalAddress)
	if identity.ConnectorKey != GmailConnectedMailboxKey || identity.Generation <= 0 ||
		!isBareEmailAddress(identity.PrincipalAddress) {
		return nil, fmt.Errorf("connected Gmail account is not usable")
	}
	completion := AcceptedEmailCompletion{
		Version:   sendRecordEmailCompletionVersion,
		RequestID: input.RequestID, TaskID: sendRecordEmailDeterministicID(input.RequestID, "task"),
		EmailMessageID: sendRecordEmailDeterministicID(input.RequestID, "email-message"),
		UserID:         userID, ObjectAPIName: input.ObjectAPIName, RecordID: input.RecordID,
		FromAddress: identity.PrincipalAddress, ToAddress: recipientAddress,
		Subject: input.Subject, TextBody: input.Body,
	}
	snapshot, err := json.Marshal(completion)
	if err != nil {
		return nil, fmt.Errorf("freeze email completion: %w", err)
	}
	rawMessage, err := buildGmailRawMessage(ConnectedMailboxEmail{
		From: identity.PrincipalAddress, To: recipientAddress,
		Subject: input.Subject, Body: input.Body,
	})
	if err != nil {
		return nil, err
	}
	delivery, err := deps.Delivery.Dispatch(ctx, connectordelivery.Request{
		RequestID: input.RequestID, UserID: userID, ConnectorKey: GmailConnectedMailboxKey,
		ExpectedGeneration: identity.Generation, Payload: rawMessage,
		SourceObject: input.ObjectAPIName, SourceRecordID: input.RecordID,
		CompletionSnapshot: snapshot,
	})
	if err != nil {
		return nil, fmt.Errorf("send email through connected Gmail account: %w", err)
	}
	return finishAcceptedEmail(ctx, deps, delivery, input, userID, user)
}

func SendRecordEmailActivityCreateFields(objectAPIName string) []string {
	var fields []string
	switch objectAPIName {
	case "Task":
		fields = []string{"WhoId", "Subject", "ActivityDate", "Status", "Priority", "Description", "OwnerId", "Type", "TaskSubtype"}
	case "EmailMessage":
		fields = []string{"ActivityId", "FromAddress", "ToAddress", "Subject", "TextBody", "Incoming", "Status", "MessageDate"}
	}
	return fields
}

func finishAcceptedEmail(ctx context.Context, deps SendRecordEmailDeps, delivery connectordelivery.Result, input SendRecordEmailInput, userID string, user *models.UserSession) (*SendRecordEmailResult, error) {
	storedCompletion, err := decodeAcceptedEmailCompletion(delivery.CompletionSnapshot)
	if err != nil {
		return nil, fmt.Errorf("read frozen email completion: %w", err)
	}
	if storedCompletion.RequestID != input.RequestID || storedCompletion.UserID != userID ||
		storedCompletion.TaskID != sendRecordEmailDeterministicID(input.RequestID, "task") ||
		storedCompletion.EmailMessageID != sendRecordEmailDeterministicID(input.RequestID, "email-message") ||
		storedCompletion.RecordID != input.RecordID || storedCompletion.ObjectAPIName != input.ObjectAPIName ||
		storedCompletion.Subject != input.Subject || storedCompletion.TextBody != input.Body {
		return nil, fmt.Errorf("frozen email completion does not match this action input and user")
	}
	result := &SendRecordEmailResult{
		TaskID: storedCompletion.TaskID, EmailMessageID: storedCompletion.EmailMessageID,
		ProviderMessageID: delivery.RemoteReceiptID, ProviderThreadID: delivery.RemoteConversationID,
	}
	if (delivery.State != connectordelivery.StateAccepted && delivery.State != connectordelivery.StateCompleted) ||
		delivery.AcceptedAt == nil || strings.TrimSpace(delivery.RemoteReceiptID) == "" ||
		delivery.ResponseStatus < 200 || delivery.ResponseStatus >= 300 {
		return nil, fmt.Errorf("connected Gmail delivery returned invalid accepted evidence")
	}
	if delivery.State == connectordelivery.StateCompleted {
		return result, nil
	}
	acceptedAt := delivery.AcceptedAt.UTC()
	completionCtx, cancel := context.WithTimeout(context.WithoutCancel(ctx), sendRecordEmailCompletionTimeout)
	defer cancel()
	if err := deps.RecordWriter.EnsureImmutableRecords(completionCtx,
		acceptedEmailRecords(storedCompletion, acceptedAt), user); err != nil {
		return nil, fmt.Errorf("email was accepted by Gmail but CRM activity completion is pending; retry with the same request ID: %w", err)
	}
	completed, err := deps.Delivery.Complete(completionCtx, input.RequestID, userID)
	if err != nil {
		return nil, fmt.Errorf("email activity was written but delivery completion is pending; retry with the same request ID: %w", err)
	}
	if completed.State != connectordelivery.StateCompleted {
		return nil, fmt.Errorf("connected Gmail delivery completion returned invalid state %q", completed.State)
	}
	return result, nil
}

func acceptedEmailRecords(completion AcceptedEmailCompletion, acceptedAt time.Time) []ImmutableRecord {
	taskSubject := sendRecordEmailTaskSubject
	if completion.Version == sendRecordEmailLegacyCompletionVersion {
		taskSubject = completion.Subject
	}
	return []ImmutableRecord{
		{
			ObjectAPIName: "Task",
			RecordID:      completion.TaskID,
			Fields: contract.Record{
				"WhoId": completion.RecordID, "Subject": taskSubject,
				"ActivityDate": acceptedAt.UTC().Format(time.DateOnly), "Status": "Completed",
				"Priority": "Normal", "Description": completion.TextBody, "OwnerId": completion.UserID,
				"Type": "Email", "TaskSubtype": "Email",
			},
		},
		{
			ObjectAPIName: "EmailMessage",
			RecordID:      completion.EmailMessageID,
			Fields: contract.Record{
				"ActivityId": completion.TaskID, "FromAddress": completion.FromAddress,
				"ToAddress": completion.ToAddress, "Subject": completion.Subject,
				"TextBody": completion.TextBody, "Incoming": false, "Status": "3",
				"MessageDate": acceptedAt.UTC().Format(time.RFC3339Nano),
			},
		},
	}
}

func decodeAcceptedEmailCompletion(raw json.RawMessage) (AcceptedEmailCompletion, error) {
	var completion AcceptedEmailCompletion
	if len(raw) == 0 || json.Unmarshal(raw, &completion) != nil ||
		(completion.Version != sendRecordEmailLegacyCompletionVersion && completion.Version != sendRecordEmailCompletionVersion) ||
		completion.RequestID == "" || completion.TaskID == "" || completion.EmailMessageID == "" ||
		completion.UserID == "" || completion.RecordID == "" || completion.FromAddress == "" ||
		(completion.ObjectAPIName != "Contact" && completion.ObjectAPIName != "Lead") ||
		!isBareEmailAddress(completion.FromAddress) || !isBareEmailAddress(completion.ToAddress) ||
		completion.Subject == "" || strings.ContainsAny(completion.Subject, "\r\n") || completion.TextBody == "" {
		return AcceptedEmailCompletion{}, fmt.Errorf("frozen completion is invalid")
	}
	return completion, nil
}

func sendRecordEmailString(value any) string {
	result, _ := value.(string)
	return result
}

func sendRecordEmailDeterministicID(requestID, kind string) string {
	digest := sha256.Sum256([]byte("nexuscrm:send-record-email:" + kind + ":" + requestID))
	value := digest[:16]
	value[6] = (value[6] & 0x0f) | 0x80
	value[8] = (value[8] & 0x3f) | 0x80
	return fmt.Sprintf("%08x-%04x-%04x-%04x-%012x",
		value[0:4], value[4:6], value[6:8], value[8:10], value[10:16])
}

func isBareEmailAddress(value string) bool {
	value = strings.TrimSpace(value)
	parsed, err := mail.ParseAddress(value)
	return err == nil && parsed.Address == value
}
