package routing

import (
	"context"
	"strings"
	"sync"
	"time"
)

type memoryStickyEntry struct {
	binding   StickyBinding
	expiresAt time.Time
}

type memoryRouteFailureEntry struct {
	state     RouteFailureState
	expiresAt time.Time
}

type memoryRouteCooldownEntry struct {
	state     RouteCooldownState
	expiresAt time.Time
}

type InMemoryStickyStore struct {
	now          func() time.Time
	mu           sync.RWMutex
	bindings     map[string]memoryStickyEntry
	routeFailure map[string]memoryRouteFailureEntry
	cooldowns    map[string]memoryRouteCooldownEntry
}

func NewInMemoryStickyStore() *InMemoryStickyStore {
	return &InMemoryStickyStore{
		now:          time.Now,
		bindings:     make(map[string]memoryStickyEntry),
		routeFailure: make(map[string]memoryRouteFailureEntry),
		cooldowns:    make(map[string]memoryRouteCooldownEntry),
	}
}

func (s *InMemoryStickyStore) Get(_ context.Context, key string) (StickyBinding, bool, error) {
	key = strings.TrimSpace(key)
	if key == "" {
		return StickyBinding{}, false, nil
	}

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

	entry, ok := s.bindings[key]
	if !ok {
		return StickyBinding{}, false, nil
	}
	if s.expired(entry.expiresAt) {
		delete(s.bindings, key)
		return StickyBinding{}, false, nil
	}
	return entry.binding, true, nil
}

func (s *InMemoryStickyStore) Set(_ context.Context, key string, binding StickyBinding, ttl time.Duration) error {
	key = strings.TrimSpace(key)
	binding, err := normalizeStickyBinding(binding, ttl, s.now())
	if err != nil {
		return err
	}
	if key == "" {
		return nil
	}

	s.mu.Lock()
	defer s.mu.Unlock()
	s.bindings[key] = memoryStickyEntry{binding: binding, expiresAt: s.now().UTC().Add(ttl)}
	return nil
}

func (s *InMemoryStickyStore) Delete(_ context.Context, key string) error {
	key = strings.TrimSpace(key)
	if key == "" {
		return nil
	}
	s.mu.Lock()
	defer s.mu.Unlock()
	delete(s.bindings, key)
	return nil
}

func (s *InMemoryStickyStore) GetRouteFailure(_ context.Context, routeID string) (RouteFailureState, bool, error) {
	routeID = strings.TrimSpace(routeID)
	if routeID == "" {
		return RouteFailureState{}, false, nil
	}

	s.mu.Lock()
	defer s.mu.Unlock()
	entry, ok := s.routeFailure[routeID]
	if !ok {
		return RouteFailureState{}, false, nil
	}
	if s.expired(entry.expiresAt) {
		delete(s.routeFailure, routeID)
		return RouteFailureState{}, false, nil
	}
	return entry.state, true, nil
}

func (s *InMemoryStickyStore) SetRouteFailure(_ context.Context, routeID string, state RouteFailureState, ttl time.Duration) error {
	state, err := normalizeRouteFailureState(routeID, state, ttl, s.now())
	if err != nil {
		return err
	}

	s.mu.Lock()
	defer s.mu.Unlock()
	s.routeFailure[state.RouteID] = memoryRouteFailureEntry{state: state, expiresAt: s.now().UTC().Add(ttl)}
	return nil
}

func (s *InMemoryStickyStore) ClearRouteFailure(_ context.Context, routeID string) error {
	routeID = strings.TrimSpace(routeID)
	if routeID == "" {
		return nil
	}
	s.mu.Lock()
	defer s.mu.Unlock()
	delete(s.routeFailure, routeID)
	return nil
}

func (s *InMemoryStickyStore) GetCooldown(_ context.Context, routeID string) (RouteCooldownState, bool, error) {
	routeID = strings.TrimSpace(routeID)
	if routeID == "" {
		return RouteCooldownState{}, false, nil
	}

	s.mu.Lock()
	defer s.mu.Unlock()
	entry, ok := s.cooldowns[routeID]
	if !ok {
		return RouteCooldownState{}, false, nil
	}
	if s.expired(entry.expiresAt) {
		delete(s.cooldowns, routeID)
		return RouteCooldownState{}, false, nil
	}
	return entry.state, true, nil
}

func (s *InMemoryStickyStore) SetCooldown(_ context.Context, routeID string, state RouteCooldownState, ttl time.Duration) error {
	state, err := normalizeRouteCooldownState(routeID, state, ttl, s.now())
	if err != nil {
		return err
	}

	s.mu.Lock()
	defer s.mu.Unlock()
	s.cooldowns[state.RouteID] = memoryRouteCooldownEntry{state: state, expiresAt: s.now().UTC().Add(ttl)}
	return nil
}

func (s *InMemoryStickyStore) ClearCooldown(_ context.Context, routeID string) error {
	routeID = strings.TrimSpace(routeID)
	if routeID == "" {
		return nil
	}
	s.mu.Lock()
	defer s.mu.Unlock()
	delete(s.cooldowns, routeID)
	return nil
}

func (s *InMemoryStickyStore) expired(expiresAt time.Time) bool {
	return !expiresAt.IsZero() && !expiresAt.After(s.now().UTC())
}
