package oobs

import (
	"strings"
	"sync"

	"github.com/tech/sendico/pkg/merrors"
)

type memoryAuditStore struct {
	mu        sync.RWMutex
	byPayment map[string][]TimelineEntry
	byStep    map[string][]TimelineEntry
}

func newMemoryAuditStore() AuditStore {
	return &memoryAuditStore{
		byPayment: map[string][]TimelineEntry{},
		byStep:    map[string][]TimelineEntry{},
	}
}

func (s *memoryAuditStore) Append(entry TimelineEntry) error {
	paymentRef := strings.TrimSpace(entry.PaymentRef)
	if paymentRef == "" {
		return merrors.InvalidArgument("timeline.payment_ref is required")
	}
	stepRef := strings.TrimSpace(entry.StepRef)
	entry.PaymentRef = paymentRef
	entry.StepRef = stepRef
	entry.StepCode = strings.TrimSpace(entry.StepCode)
	entry.Event = strings.TrimSpace(entry.Event)
	entry.State = strings.TrimSpace(entry.State)
	entry.Message = strings.TrimSpace(entry.Message)
	entry.Fields = trimStringMap(entry.Fields)

	s.mu.Lock()
	defer s.mu.Unlock()

	s.byPayment[paymentRef] = append(s.byPayment[paymentRef], cloneTimelineEntry(entry))
	if stepRef == "" {
		return nil
	}
	key := stepAttemptKey(paymentRef, stepRef, entry.Attempt)
	s.byStep[key] = append(s.byStep[key], cloneTimelineEntry(entry))
	return nil
}

func (s *memoryAuditStore) ListByPayment(paymentRef string, limit int32, desc bool) ([]TimelineEntry, error) {
	ref := strings.TrimSpace(paymentRef)
	if ref == "" {
		return nil, merrors.InvalidArgument("payment_ref is required")
	}
	limit = normalizeLimit(limit)

	s.mu.RLock()
	items := append([]TimelineEntry(nil), s.byPayment[ref]...)
	s.mu.RUnlock()

	return paginateEntries(items, limit, desc), nil
}

func (s *memoryAuditStore) ListByStepAttempt(
	paymentRef string,
	stepRef string,
	attempt uint32,
	limit int32,
	desc bool,
) ([]TimelineEntry, error) {
	ref := strings.TrimSpace(paymentRef)
	if ref == "" {
		return nil, merrors.InvalidArgument("payment_ref is required")
	}
	step := strings.TrimSpace(stepRef)
	if step == "" {
		return nil, merrors.InvalidArgument("step_ref is required")
	}
	if attempt == 0 {
		return nil, merrors.InvalidArgument("attempt is required")
	}
	limit = normalizeLimit(limit)

	key := stepAttemptKey(ref, step, attempt)
	s.mu.RLock()
	items := append([]TimelineEntry(nil), s.byStep[key]...)
	s.mu.RUnlock()

	return paginateEntries(items, limit, desc), nil
}

func paginateEntries(items []TimelineEntry, limit int32, desc bool) []TimelineEntry {
	if desc {
		items = reverseEntries(items)
	}
	if len(items) == 0 {
		return nil
	}
	if limit > 0 && len(items) > int(limit) {
		items = items[:int(limit)]
	}
	out := make([]TimelineEntry, 0, len(items))
	for i := range items {
		out = append(out, cloneTimelineEntry(items[i]))
	}
	return out
}

func cloneTimelineEntry(entry TimelineEntry) TimelineEntry {
	entry.Fields = cloneStringMap(entry.Fields)
	return entry
}
