package store

import (
	"math/rand"
	"sort"
	"strings"
	"sync"
	"time"

	"abuse_registration_poc/internal/models"
	"abuse_registration_poc/internal/utils"
)

type MemoryStore struct {
	mu            sync.RWMutex
	rows          []models.Registration
	resetInterval time.Duration
	lastReset     time.Time
	nextReset     time.Time
	baseSize      int
}

func NewMemoryStore(baseSize int, resetInterval time.Duration) *MemoryStore {
	if baseSize < 1 {
		baseSize = 546
	}
	if resetInterval < time.Second {
		resetInterval = 10 * time.Minute
	}

	s := &MemoryStore{baseSize: baseSize, resetInterval: resetInterval}
	s.Reset()
	return s
}

func (s *MemoryStore) ResetInterval() time.Duration {
	return s.resetInterval
}

func (s *MemoryStore) Reset() {
	s.mu.Lock()
	defer s.mu.Unlock()

	now := time.Now().UTC()
	s.rows = generateBaseDataset(s.baseSize)
	s.lastReset = now
	s.nextReset = now.Add(s.resetInterval)
}

func (s *MemoryStore) Snapshot() ([]models.Registration, time.Time, time.Time) {
	s.resetIfNeeded()
	s.mu.RLock()
	defer s.mu.RUnlock()

	rows := make([]models.Registration, len(s.rows))
	copy(rows, s.rows)
	return rows, s.lastReset, s.nextReset
}

func (s *MemoryStore) List(filter models.RegistrationFilter) []models.Registration {
	rows, _, _ := s.Snapshot()
	return applyFilter(rows, filter)
}

func (s *MemoryStore) Get(id string) (models.Registration, bool) {
	s.resetIfNeeded()
	s.mu.RLock()
	defer s.mu.RUnlock()
	for _, row := range s.rows {
		if row.ID == id {
			return row, true
		}
	}
	return models.Registration{}, false
}

func (s *MemoryStore) Create(input models.Registration) (models.Registration, error) {
	s.resetIfNeeded()
	if err := models.NormalizeRegistration(&input); err != nil {
		return models.Registration{}, err
	}

	s.mu.Lock()
	defer s.mu.Unlock()
	input.ID = utils.NewUUID()
	s.rows = append(s.rows, input)
	return input, nil
}

func (s *MemoryStore) Replace(id string, input models.Registration) (models.Registration, bool, error) {
	s.resetIfNeeded()
	if err := models.NormalizeRegistration(&input); err != nil {
		return models.Registration{}, false, err
	}

	s.mu.Lock()
	defer s.mu.Unlock()
	for i := range s.rows {
		if s.rows[i].ID == id {
			input.ID = id
			s.rows[i] = input
			return input, true, nil
		}
	}
	return models.Registration{}, false, nil
}

func (s *MemoryStore) Delete(id string) bool {
	s.resetIfNeeded()
	s.mu.Lock()
	defer s.mu.Unlock()
	for i := range s.rows {
		if s.rows[i].ID == id {
			s.rows = append(s.rows[:i], s.rows[i+1:]...)
			return true
		}
	}
	return false
}

func (s *MemoryStore) resetIfNeeded() {
	s.mu.RLock()
	due := time.Now().UTC().After(s.nextReset)
	s.mu.RUnlock()
	if due {
		s.Reset()
	}
}

func applyFilter(rows []models.Registration, filter models.RegistrationFilter) []models.Registration {
	location := strings.ToLower(strings.TrimSpace(filter.Location))
	abuseType := strings.ToLower(strings.TrimSpace(filter.AbuseType))
	gender := strings.ToLower(strings.TrimSpace(filter.Gender))
	status := strings.ToLower(strings.TrimSpace(filter.Status))
	search := strings.ToLower(strings.TrimSpace(filter.Search))

	out := make([]models.Registration, 0, len(rows))
	for _, row := range rows {
		if location != "" && strings.ToLower(row.Location) != location {
			continue
		}
		if abuseType != "" && strings.ToLower(row.AbuseType) != abuseType {
			continue
		}
		if gender != "" && strings.ToLower(row.Gender) != gender {
			continue
		}
		if status != "" && strings.ToLower(row.Status) != status {
			continue
		}
		if filter.HasFrom || filter.HasTo {
			registeredAt, err := time.Parse(time.RFC3339, row.RegisteredAt)
			if err != nil {
				continue
			}
			if filter.HasFrom && registeredAt.Before(filter.From) {
				continue
			}
			if filter.HasTo && registeredAt.After(filter.To) {
				continue
			}
		}
		if search != "" && !strings.Contains(strings.ToLower(row.ID+" "+row.Location+" "+row.AbuseType+" "+row.Gender+" "+row.Status), search) {
			continue
		}
		out = append(out, row)
	}

	sort.SliceStable(out, func(i, j int) bool { return out[i].RegisteredAt < out[j].RegisteredAt })

	if filter.Offset > 0 {
		if filter.Offset >= len(out) {
			return []models.Registration{}
		}
		out = out[filter.Offset:]
	}
	if filter.Limit > 0 && filter.Limit < len(out) {
		out = out[:filter.Limit]
	}
	return out
}

func generateBaseDataset(count int) []models.Registration {
	seed := rand.New(rand.NewSource(19881991))
	locations := models.FaroeLocations
	rows := make([]models.Registration, 0, count)
	for i := 0; i < count; i++ {
		location := locations[i%len(locations)]
		if i >= len(locations) {
			location = locations[seed.Intn(len(locations))]
		}
		rows = append(rows, models.Registration{
			ID:           utils.NewUUID(),
			RegisteredAt: randomRegistrationTime(seed).Format(time.RFC3339),
			Gender:       models.Genders[seed.Intn(len(models.Genders))],
			Location:     location,
			AbuseType:    models.AbuseCategories[seed.Intn(len(models.AbuseCategories))],
			Status:       models.Statuses[seed.Intn(len(models.Statuses))],
		})
	}
	return rows
}

func randomRegistrationTime(seed *rand.Rand) time.Time {
	start := time.Date(1988, 1, 1, 0, 0, 0, 0, time.UTC)
	end := time.Date(1991, 12, 31, 23, 59, 59, 0, time.UTC)
	span := end.Unix() - start.Unix()
	return time.Unix(start.Unix()+seed.Int63n(span), 0).UTC()
}
