package manage

import (
	"crypto/sha256"
	"encoding/hex"
	"encoding/json"
	"fmt"
	"strings"
	"time"

	execschedule "github.com/tidbcloud/AgentTeam/internal/schedule"
	"github.com/tidbcloud/AgentTeam/internal/store/message"
	"github.com/tidbcloud/AgentTeam/internal/store/schedule"
)

const scheduleMutationMetadataKey = "agentteamAgentMutation"

type scheduleMutationMetadata struct {
	Version        int    `json:"version"`
	IdempotencyKey string `json:"idempotencyKey,omitempty"`
	Fingerprint    string `json:"fingerprint,omitempty"`
}

type scheduleMutationFingerprintPayload struct {
	Name                  string                               `json:"name"`
	SourceKind            string                               `json:"sourceKind"`
	TimeMode              string                               `json:"timeMode"`
	ScheduledAt           string                               `json:"scheduledAt,omitempty"`
	RelativeSeconds       *int64                               `json:"relativeSeconds,omitempty"`
	AnchorAt              string                               `json:"anchorAt,omitempty"`
	IntervalSeconds       *int64                               `json:"intervalSeconds,omitempty"`
	CronExpression        string                               `json:"cronExpression,omitempty"`
	Timezone              string                               `json:"timezone,omitempty"`
	TargetKind            string                               `json:"targetKind"`
	TargetRef             string                               `json:"targetRef"`
	MessageTemplate       string                               `json:"messageTemplate"`
	Mentions              []scheduleMutationFingerprintMention `json:"mentions,omitempty"`
	AlsoStartThread       bool                                 `json:"alsoStartThread,omitempty"`
	HumanNotificationMode string                               `json:"humanNotificationMode,omitempty"`
}

type scheduleMutationFingerprintMention struct {
	Type     string         `json:"type"`
	ID       string         `json:"id"`
	Token    string         `json:"token"`
	Metadata map[string]any `json:"metadata,omitempty"`
}

func scheduleMutationFingerprint(record definitionRecord) (string, error) {
	payload := scheduleMutationFingerprintPayload{
		Name:                  strings.TrimSpace(record.Name),
		SourceKind:            string(record.SourceKind),
		TimeMode:              string(record.TimeMode),
		RelativeSeconds:       record.RelativeSeconds,
		IntervalSeconds:       record.IntervalSeconds,
		CronExpression:        strings.TrimSpace(record.CronExpression),
		Timezone:              strings.TrimSpace(record.Timezone),
		TargetKind:            string(record.TargetKind),
		TargetRef:             strings.TrimSpace(record.TargetRef),
		MessageTemplate:       strings.TrimSpace(record.MessageTemplate),
		Mentions:              scheduleMutationFingerprintMentions(record.MentionHints),
		AlsoStartThread:       record.AlsoStartThread && record.TargetKind == TargetKindChannel,
		HumanNotificationMode: string(record.HumanNotificationMode),
	}
	if record.ScheduledAt != nil && record.TimeMode != TimeModeOneShotRelative {
		// Relative one-shot requests are keyed by the canonical relative input.
		// scheduledAt is materialized from database wall clock on write, so
		// including the stored value would make mutation fingerprints diverge
		// from the original request payload.
		payload.ScheduledAt = record.ScheduledAt.UTC().Format(time.RFC3339Nano)
	}
	if record.AnchorAt != nil {
		payload.AnchorAt = record.AnchorAt.UTC().Format(time.RFC3339Nano)
	}
	data, err := json.Marshal(payload)
	if err != nil {
		return "", fmt.Errorf("marshal schedule mutation fingerprint: %w", err)
	}
	sum := sha256.Sum256(data)
	return "sha256:" + hex.EncodeToString(sum[:]), nil
}

func scheduleMutationFingerprintMentions(hints []message.MessageMentionHint) []scheduleMutationFingerprintMention {
	if len(hints) == 0 {
		return nil
	}
	mentions := make([]scheduleMutationFingerprintMention, 0, len(hints))
	for _, hint := range hints {
		mentionType := strings.ToLower(strings.TrimSpace(hint.Type))
		id := strings.TrimSpace(hint.ID)
		token := strings.TrimSpace(hint.Token)
		if mentionType == "" || id == "" || token == "" {
			continue
		}
		mentions = append(mentions, scheduleMutationFingerprintMention{
			Type:     mentionType,
			ID:       id,
			Token:    strings.ToLower(token),
			Metadata: normalizedScheduleMutationMentionMetadata(hint.Metadata),
		})
	}
	return mentions
}

func normalizedScheduleMutationMentionMetadata(metadata map[string]any) map[string]any {
	if len(metadata) == 0 {
		return nil
	}
	cloned := make(map[string]any, len(metadata))
	for key, value := range metadata {
		cloned[key] = value
	}
	return cloned
}

func mergeScheduleMutationMetadata(existing []byte, record definitionRecord, idempotencyKey string) ([]byte, error) {
	object, err := decodeScheduleMetadataObject(existing)
	if err != nil {
		return nil, err
	}
	object = execschedule.PutMessageOptionsMetadata(object, execschedule.MessageOptions{
		AlsoStartThread: record.AlsoStartThread && record.TargetKind == TargetKindChannel,
		MentionHints:    record.MentionHints,
	})
	trimmedKey := strings.TrimSpace(idempotencyKey)
	if trimmedKey == "" {
		delete(object, scheduleMutationMetadataKey)
	} else {
		fingerprint, err := scheduleMutationFingerprint(record)
		if err != nil {
			return nil, err
		}
		object[scheduleMutationMetadataKey] = scheduleMutationMetadata{
			Version:        1,
			IdempotencyKey: trimmedKey,
			Fingerprint:    fingerprint,
		}
	}
	if len(object) == 0 {
		return nil, nil
	}
	data, err := json.Marshal(object)
	if err != nil {
		return nil, fmt.Errorf("marshal schedule metadata: %w", err)
	}
	return data, nil
}

func scheduleMutationMatchesExisting(existing schedule.Definition, record definitionRecord, idempotencyKey string) (bool, error) {
	trimmedKey := strings.TrimSpace(idempotencyKey)
	if trimmedKey == "" {
		return false, nil
	}
	if strings.TrimSpace(existing.IdempotencyKey) == trimmedKey {
		existingFingerprint, err := scheduleMutationFingerprint(definitionRecordFromScheduleDefinition("", existing))
		if err != nil {
			return false, err
		}
		fingerprint, err := scheduleMutationFingerprint(record)
		if err != nil {
			return false, err
		}
		if existingFingerprint == fingerprint {
			return true, nil
		}
		return false, conflict("schedule mutation idempotency key already used for a different payload")
	}
	metadata, ok, err := decodeScheduleMutationMetadata(existing.Metadata)
	if err != nil {
		return false, err
	}
	if !ok || strings.TrimSpace(metadata.IdempotencyKey) == "" {
		return false, nil
	}
	if strings.TrimSpace(metadata.IdempotencyKey) != trimmedKey {
		return false, nil
	}
	fingerprint, err := scheduleMutationFingerprint(record)
	if err != nil {
		return false, err
	}
	if strings.TrimSpace(metadata.Fingerprint) == "" || strings.TrimSpace(metadata.Fingerprint) == fingerprint {
		return true, nil
	}
	return false, conflict("schedule mutation idempotency key already used for a different payload")
}

func decodeScheduleMetadataObject(existing []byte) (map[string]any, error) {
	if len(existing) == 0 {
		return map[string]any{}, nil
	}
	var object map[string]any
	if err := json.Unmarshal(existing, &object); err != nil {
		return nil, fmt.Errorf("decode schedule metadata: %w", err)
	}
	if object == nil {
		object = map[string]any{}
	}
	return object, nil
}

func decodeScheduleMutationMetadata(existing []byte) (scheduleMutationMetadata, bool, error) {
	object, err := decodeScheduleMetadataObject(existing)
	if err != nil {
		return scheduleMutationMetadata{}, false, err
	}
	raw, ok := object[scheduleMutationMetadataKey]
	if !ok || raw == nil {
		return scheduleMutationMetadata{}, false, nil
	}
	data, err := json.Marshal(raw)
	if err != nil {
		return scheduleMutationMetadata{}, false, fmt.Errorf("marshal schedule mutation metadata: %w", err)
	}
	var metadata scheduleMutationMetadata
	if err := json.Unmarshal(data, &metadata); err != nil {
		return scheduleMutationMetadata{}, false, fmt.Errorf("decode schedule mutation metadata: %w", err)
	}
	return metadata, true, nil
}
