package agentstate

import (
	"fmt"
	"reflect"
	"sort"
	"strings"
	"sync"
	"time"
)

const (
	DefaultTombstoneTTL      = 24 * time.Hour
	DefaultTombstoneCapacity = 512
)

type Tombstones struct {
	mu sync.Mutex

	ttl      time.Duration
	capacity int
	entries  map[string]WakeDeliveryState
}

func NewTombstones(ttl time.Duration, capacity int) *Tombstones {
	if ttl <= 0 {
		ttl = DefaultTombstoneTTL
	}
	if capacity <= 0 {
		capacity = DefaultTombstoneCapacity
	}
	return &Tombstones{
		ttl:      ttl,
		capacity: capacity,
		entries:  make(map[string]WakeDeliveryState),
	}
}

func (set *Tombstones) Add(state WakeDeliveryState) error {
	if set == nil {
		return fmt.Errorf("tombstone set is nil")
	}
	if err := state.Validate(); err != nil {
		return err
	}
	if !wakeIsTerminal(state.Phase) {
		return fmt.Errorf("tombstone requires terminal wake, got %q", state.Phase)
	}
	state = cloneWakeDelivery(state)

	set.mu.Lock()
	defer set.mu.Unlock()
	if current, exists := set.entries[state.LocalWakeID]; exists {
		if reflect.DeepEqual(current, state) {
			return nil
		}
		return ErrTerminalWake
	}
	set.entries[state.LocalWakeID] = state
	return nil
}

func (set *Tombstones) Evict(now time.Time) []string {
	if set == nil {
		return nil
	}
	now = now.UTC()
	set.mu.Lock()
	defer set.mu.Unlock()

	evicted := make(map[string]struct{})
	for id, state := range set.entries {
		if !state.ChangedAt.Add(set.ttl).After(now) {
			delete(set.entries, id)
			evicted[id] = struct{}{}
		}
	}
	if excess := len(set.entries) - set.capacity; excess > 0 {
		oldest := make([]WakeDeliveryState, 0, len(set.entries))
		for _, state := range set.entries {
			oldest = append(oldest, state)
		}
		sort.Slice(oldest, func(left, right int) bool {
			if oldest[left].ChangedAt.Equal(oldest[right].ChangedAt) {
				return strings.TrimSpace(oldest[left].LocalWakeID) >
					strings.TrimSpace(oldest[right].LocalWakeID)
			}
			return oldest[left].ChangedAt.Before(oldest[right].ChangedAt)
		})
		for index := 0; index < excess; index++ {
			id := oldest[index].LocalWakeID
			delete(set.entries, id)
			evicted[id] = struct{}{}
		}
	}
	result := make([]string, 0, len(evicted))
	for id := range evicted {
		result = append(result, id)
	}
	sort.Strings(result)
	return result
}

func (set *Tombstones) Snapshot() []WakeDeliveryState {
	if set == nil {
		return nil
	}
	set.mu.Lock()
	defer set.mu.Unlock()

	result := make([]WakeDeliveryState, 0, len(set.entries))
	for _, state := range set.entries {
		result = append(result, cloneWakeDelivery(state))
	}
	sort.Slice(result, func(left, right int) bool {
		if result[left].ChangedAt.Equal(result[right].ChangedAt) {
			return strings.TrimSpace(result[left].LocalWakeID) <
				strings.TrimSpace(result[right].LocalWakeID)
		}
		return result[left].ChangedAt.After(result[right].ChangedAt)
	})
	return result
}

func (set *Tombstones) Len() int {
	if set == nil {
		return 0
	}
	set.mu.Lock()
	defer set.mu.Unlock()
	return len(set.entries)
}
