package store

import (
	"context"
	"math"
	"regexp"
	"sort"
	"strings"
	"sync"
	"time"
)

// InMemoryStore is an in-memory implementation of BaseStore.
// It is not thread-safe by default, use NewInMemoryStore() for a thread-safe version.
type InMemoryStore struct {
	mu            sync.RWMutex
	data          map[string]map[string]map[string]interface{}
	ttl           map[string]time.Time
	indexes       map[string]map[string][]float64 // namespaceKey -> key -> embedding vector
	indexConfigs  map[string]IndexConfig          // namespaceKey -> index config
	closed        bool
	cleanupTicker *time.Ticker
	stopCleanup   chan struct{}
}

// NewInMemoryStore creates a new thread-safe in-memory store.
// It starts a background goroutine for TTL cleanup every minute.
func NewInMemoryStore() *InMemoryStore {
	store := &InMemoryStore{
		data:         make(map[string]map[string]map[string]interface{}),
		ttl:          make(map[string]time.Time),
		indexes:      make(map[string]map[string][]float64),
		indexConfigs: make(map[string]IndexConfig),
		stopCleanup:  make(chan struct{}),
	}
	// Start TTL cleanup goroutine
	store.cleanupTicker = time.NewTicker(1 * time.Minute)
	go store.cleanupExpired()
	return store
}

// Get retrieves a value from the store.
func (s *InMemoryStore) Get(ctx context.Context, namespace []string, key string) (map[string]interface{}, error) {
	s.mu.RLock()
	defer s.mu.RUnlock()

	if s.closed {
		return nil, &StoreError{Message: "store is closed"}
	}

	nsKey := s.nsKey(namespace)
	if nsData, ok := s.data[nsKey]; ok {
		// Check TTL
		if fullKey := s.fullKey(nsKey, key); !s.checkTTL(fullKey) {
			return nil, nil
		}

		if value, ok := nsData[key]; ok {
			return s.copyValue(value), nil
		}
	}

	return nil, nil
}

// Put stores a value in the store.
func (s *InMemoryStore) Put(ctx context.Context, namespace []string, key string, value map[string]interface{}) error {
	s.mu.Lock()
	defer s.mu.Unlock()

	if s.closed {
		return &StoreError{Message: "store is closed"}
	}

	nsKey := s.nsKey(namespace)
	if _, ok := s.data[nsKey]; !ok {
		s.data[nsKey] = make(map[string]map[string]interface{})
	}

	s.data[nsKey][key] = s.copyValue(value)

	return nil
}

// Delete removes a value from the store.
func (s *InMemoryStore) Delete(ctx context.Context, namespace []string, key string) error {
	s.mu.Lock()
	defer s.mu.Unlock()

	if s.closed {
		return &StoreError{Message: "store is closed"}
	}

	nsKey := s.nsKey(namespace)
	if nsData, ok := s.data[nsKey]; ok {
		delete(nsData, key)
		fullKey := s.fullKey(nsKey, key)
		delete(s.ttl, fullKey)
	}

	return nil
}

// Search searches for values in the store.
func (s *InMemoryStore) Search(ctx context.Context, namespace []string, query string, limit int) ([]map[string]interface{}, error) {
	s.mu.RLock()
	defer s.mu.RUnlock()

	if s.closed {
		return nil, &StoreError{Message: "store is closed"}
	}

	nsKey := s.nsKey(namespace)
	nsData, ok := s.data[nsKey]
	if !ok {
		return nil, nil
	}

	results := make([]map[string]interface{}, 0)
	for key, value := range nsData {
		fullKey := s.fullKey(nsKey, key)
		if !s.checkTTL(fullKey) {
			continue
		}

		// Simple query matching (can be extended)
		if query == "" || s.matchQuery(value, query) {
			results = append(results, s.copyValue(value))
			if limit > 0 && len(results) >= limit {
				break
			}
		}
	}

	return results, nil
}

// List lists all keys in the namespace.
func (s *InMemoryStore) List(ctx context.Context, namespace []string, limit int) ([]string, error) {
	s.mu.RLock()
	defer s.mu.RUnlock()

	if s.closed {
		return nil, &StoreError{Message: "store is closed"}
	}

	nsKey := s.nsKey(namespace)
	nsData, ok := s.data[nsKey]
	if !ok {
		return nil, nil
	}

	keys := make([]string, 0, len(nsData))
	for key := range nsData {
		fullKey := s.fullKey(nsKey, key)
		if s.checkTTL(fullKey) {
			keys = append(keys, key)
			if limit > 0 && len(keys) >= limit {
				break
			}
		}
	}

	return keys, nil
}

// Batch executes multiple operations atomically.
func (s *InMemoryStore) Batch(ctx context.Context, ops []Op) ([]Result, error) {
	s.mu.Lock()
	defer s.mu.Unlock()

	if s.closed {
		return nil, &StoreError{Message: "store is closed"}
	}

	results := make([]Result, len(ops))
	for i, op := range ops {
		switch o := op.(type) {
		case GetOp:
			nsKey := s.nsKey(o.Namespace)
			if nsData, ok := s.data[nsKey]; ok {
				fullKey := s.fullKey(nsKey, o.Key)
				if !s.checkTTL(fullKey) {
					results[i] = Result{Value: nil, Error: nil}
					continue
				}
				if value, ok := nsData[o.Key]; ok {
					results[i] = Result{Value: s.copyValue(value), Error: nil}
				} else {
					results[i] = Result{Value: nil, Error: nil}
				}
			} else {
				results[i] = Result{Value: nil, Error: nil}
			}
		case PutOp:
			nsKey := s.nsKey(o.Namespace)
			if _, ok := s.data[nsKey]; !ok {
				s.data[nsKey] = make(map[string]map[string]interface{})
			}
			s.data[nsKey][o.Key] = s.copyValue(o.Value)
			if o.TTL != nil {
				fullKey := s.fullKey(nsKey, o.Key)
				s.ttl[fullKey] = time.Now().Add(*o.TTL)
			}
			results[i] = Result{Value: nil, Error: nil}
		case SearchOp:
			// Simplified search for batch operation
			nsKey := s.nsKey(o.NamespacePrefix)
			nsData, ok := s.data[nsKey]
			if !ok {
				results[i] = Result{Value: nil, Error: nil}
				continue
			}
			searchResults := make([]map[string]interface{}, 0)
			for key, value := range nsData {
				fullKey := s.fullKey(nsKey, key)
				if !s.checkTTL(fullKey) {
					continue
				}
				if o.Query != nil && *o.Query != "" && !s.matchQuery(value, *o.Query) {
					continue
				}
				if o.Filter != nil && !s.matchFilter(value, o.Filter) {
					continue
				}
				searchResults = append(searchResults, s.copyValue(value))
				if o.Limit > 0 && len(searchResults) >= o.Limit {
					break
				}
			}
			// Apply offset
			if o.Offset > 0 && o.Offset < len(searchResults) {
				searchResults = searchResults[o.Offset:]
			}
			results[i] = Result{Value: searchResults, Error: nil}
		case ListNamespacesOp:
			// Implement ListNamespacesOp
			namespaces, err := s.ListNamespaces(ctx, o.MatchConditions, o.MaxDepth, o.Limit, o.Offset)
			if err != nil {
				results[i] = Result{Value: nil, Error: err}
			} else {
				results[i] = Result{Value: namespaces, Error: nil}
			}
		default:
			results[i] = Result{Value: nil, Error: &StoreError{Message: "unknown operation type"}}
		}
	}
	return results, nil
}

// BatchPut performs multiple put operations atomically (deprecated).
func (s *InMemoryStore) BatchPut(ctx context.Context, ops []PutOperation) error {
	s.mu.Lock()
	defer s.mu.Unlock()

	if s.closed {
		return &StoreError{Message: "store is closed"}
	}

	for _, op := range ops {
		nsKey := s.nsKey(op.Namespace)
		if _, ok := s.data[nsKey]; !ok {
			s.data[nsKey] = make(map[string]map[string]interface{})
		}
		s.data[nsKey][op.Key] = s.copyValue(op.Value)
	}

	return nil
}

// SetTTL sets a time-to-live for a key.
func (s *InMemoryStore) SetTTL(ctx context.Context, namespace []string, key string, ttl time.Duration) error {
	s.mu.Lock()
	defer s.mu.Unlock()

	if s.closed {
		return &StoreError{Message: "store is closed"}
	}

	if ttl <= 0 {
		return nil
	}

	fullKey := s.fullKey(s.nsKey(namespace), key)
	s.ttl[fullKey] = time.Now().Add(ttl)

	return nil
}

// GetItem retrieves a value with metadata (created_at, updated_at).
func (s *InMemoryStore) GetItem(ctx context.Context, namespace []string, key string, refreshTTL *bool) (*Item, error) {
	s.mu.RLock()
	defer s.mu.RUnlock()

	if s.closed {
		return nil, &StoreError{Message: "store is closed"}
	}

	nsKey := s.nsKey(namespace)
	if nsData, ok := s.data[nsKey]; ok {
		fullKey := s.fullKey(nsKey, key)
		if !s.checkTTL(fullKey) {
			return nil, nil
		}
		if value, ok := nsData[key]; ok {
			// In memory store doesn't track created_at/updated_at, use current time
			now := time.Now()
			item := &Item{
				Value:     s.copyValue(value),
				Key:       key,
				Namespace: namespace,
				CreatedAt: now,
				UpdatedAt: now,
			}
			if expiry, ok := s.ttl[fullKey]; ok {
				item.ExpiresAt = &expiry
			}
			return item, nil
		}
	}
	return nil, nil
}

// PutItem stores a value with TTL and indexing options.
func (s *InMemoryStore) PutItem(ctx context.Context, namespace []string, key string, value map[string]interface{},
	index interface{}, ttl *time.Duration) error {
	s.mu.Lock()
	defer s.mu.Unlock()

	if s.closed {
		return &StoreError{Message: "store is closed"}
	}

	nsKey := s.nsKey(namespace)
	if _, ok := s.data[nsKey]; !ok {
		s.data[nsKey] = make(map[string]map[string]interface{})
	}
	s.data[nsKey][key] = s.copyValue(value)

	if ttl != nil {
		fullKey := s.fullKey(nsKey, key)
		s.ttl[fullKey] = time.Now().Add(*ttl)
	}

	// Handle indexing
	if index != nil {
		switch idx := index.(type) {
		case IndexConfig:
			// Store index config
			s.indexConfigs[nsKey] = idx
			// Extract embedding from value if possible
			if idx.Embed != nil && len(idx.Fields) > 0 {
				// Simplified: assume first field contains embedding vector
				for _, field := range idx.Fields {
					if emb, ok := value[field].([]float64); ok {
						if _, ok := s.indexes[nsKey]; !ok {
							s.indexes[nsKey] = make(map[string][]float64)
						}
						s.indexes[nsKey][key] = emb
						break
					}
				}
			}
		case []string:
			// Treat as list of fields to index (simplified)
			if len(idx) > 0 {
				config := IndexConfig{
					Fields: idx,
				}
				s.indexConfigs[nsKey] = config
			}
		case bool:
			if idx {
				// Enable indexing with default fields
				config := IndexConfig{
					Fields: []string{"embedding"},
				}
				s.indexConfigs[nsKey] = config
			}
		}
	}
	return nil
}

// SearchItems searches for items with advanced filtering and natural language query.
// Supports semantic search via embedding vectors in filter["$embedding"].
func (s *InMemoryStore) SearchItems(ctx context.Context, namespace []string, query *string, filter map[string]interface{},
	limit, offset int, refreshTTL *bool) ([]*SearchItem, error) {
	s.mu.RLock()
	defer s.mu.RUnlock()

	if s.closed {
		return nil, &StoreError{Message: "store is closed"}
	}

	// Check for semantic search via embedding
	if filter != nil {
		if emb, ok := filter["$embedding"].([]float64); ok {
			// Perform semantic search
			nsKey := s.nsKey(namespace)
			results := s.searchByEmbedding(nsKey, emb, limit)
			// Apply offset
			if offset > 0 && offset < len(results) {
				results = results[offset:]
			}
			return results, nil
		}
	}

	nsKey := s.nsKey(namespace)
	nsData, ok := s.data[nsKey]
	if !ok {
		return nil, nil
	}

	results := make([]*SearchItem, 0)
	now := time.Now()
	for key, value := range nsData {
		fullKey := s.fullKey(nsKey, key)
		if !s.checkTTL(fullKey) {
			continue
		}
		if query != nil && *query != "" && !s.matchQuery(value, *query) {
			continue
		}
		if filter != nil && !s.matchFilter(value, filter) {
			continue
		}
		item := &Item{
			Value:     s.copyValue(value),
			Key:       key,
			Namespace: namespace,
			CreatedAt: now,
			UpdatedAt: now,
		}
		if expiry, ok := s.ttl[fullKey]; ok {
			item.ExpiresAt = &expiry
		}
		searchItem := &SearchItem{
			Item:  item,
			Score: nil, // No scoring in this simple implementation
		}
		results = append(results, searchItem)
		if limit > 0 && len(results) >= limit {
			break
		}
	}
	// Apply offset
	if offset > 0 && offset < len(results) {
		results = results[offset:]
	}
	return results, nil
}

// ListNamespaces lists all namespaces matching given conditions.
func (s *InMemoryStore) ListNamespaces(ctx context.Context, conditions []MatchCondition, maxDepth *int,
	limit, offset int) ([][]string, error) {
	s.mu.RLock()
	defer s.mu.RUnlock()

	if s.closed {
		return nil, &StoreError{Message: "store is closed"}
	}

	namespaceSet := make(map[string][][]string)
	for nsKey := range s.data {
		parts := strings.Split(nsKey, "|")
		// Build namespace hierarchy
		for i := 1; i <= len(parts); i++ {
			prefix := strings.Join(parts[:i], "|")
			if _, ok := namespaceSet[prefix]; !ok {
				namespaceSet[prefix] = [][]string{parts[:i]}
			}
		}
	}

	// Apply conditions
	filtered := make([][]string, 0)
	for _, nsParts := range namespaceSet {
		for _, ns := range nsParts {
			if s.matchNamespaceConditions(ns, conditions) {
				filtered = append(filtered, ns)
			}
		}
	}

	// Apply maxDepth
	if maxDepth != nil {
		filtered2 := make([][]string, 0)
		for _, ns := range filtered {
			if len(ns) <= *maxDepth {
				filtered2 = append(filtered2, ns)
			}
		}
		filtered = filtered2
	}

	// Apply offset and limit
	if offset > 0 {
		filtered = filtered[offset:]
	}
	if limit > 0 && limit < len(filtered) {
		filtered = filtered[:limit]
	}

	return filtered, nil
}

// matchNamespaceConditions checks if a namespace matches the given conditions.
func (s *InMemoryStore) matchNamespaceConditions(namespace []string, conditions []MatchCondition) bool {
	if len(conditions) == 0 {
		return true
	}
	for _, cond := range conditions {
		switch cond.MatchType {
		case "prefix":
			if len(namespace) < len(cond.Path) {
				return false
			}
			for i, part := range cond.Path {
				if namespace[i] != part {
					return false
				}
			}
			return true
		case "suffix":
			if len(namespace) < len(cond.Path) {
				return false
			}
			for i, part := range cond.Path {
				if namespace[len(namespace)-len(cond.Path)+i] != part {
					return false
				}
			}
			return true
		}
	}
	return false
}

// Clear clears all data from the store.
func (s *InMemoryStore) Clear() error {
	s.mu.Lock()
	defer s.mu.Unlock()

	s.data = make(map[string]map[string]map[string]interface{})
	s.ttl = make(map[string]time.Time)

	return nil
}

// Close closes the store and stops the TTL cleanup goroutine.
func (s *InMemoryStore) Close() error {
	s.mu.Lock()
	defer s.mu.Unlock()

	if s.closed {
		return nil
	}
	s.closed = true
	if s.cleanupTicker != nil {
		s.cleanupTicker.Stop()
	}
	close(s.stopCleanup)
	return nil
}

// Helper methods

func (s *InMemoryStore) nsKey(namespace []string) string {
	return strings.Join(namespace, "|")
}

func (s *InMemoryStore) fullKey(nsKey, key string) string {
	return nsKey + ":" + key
}

func (s *InMemoryStore) checkTTL(fullKey string) bool {
	if expiry, ok := s.ttl[fullKey]; ok {
		return time.Now().Before(expiry)
	}
	return true
}

func (s *InMemoryStore) copyValue(value map[string]interface{}) map[string]interface{} {
	copied := make(map[string]interface{}, len(value))
	for k, v := range value {
		copied[k] = v
	}
	return copied
}

func (s *InMemoryStore) matchQuery(value map[string]interface{}, query string) bool {
	// Simple substring matching across all values
	// Can be extended to support more complex queries
	for _, v := range value {
		if str, ok := v.(string); ok {
			if strings.Contains(strings.ToLower(str), strings.ToLower(query)) {
				return true
			}
		}
	}
	return false
}

// matchFilter checks if a value matches the filter conditions.
// Supports comparison operators: $eq, $ne, $gt, $gte, $lt, $lte, $in, $nin, $regex
func (s *InMemoryStore) matchFilter(value map[string]interface{}, filter map[string]interface{}) bool {
	for field, condition := range filter {
		fieldValue, exists := value[field]
		if !exists {
			return false
		}

		// Handle nested operators
		if condMap, ok := condition.(map[string]interface{}); ok {
			for op, opValue := range condMap {
				if !s.compare(fieldValue, op, opValue) {
					return false
				}
			}
		} else {
			// Direct equality
			if !s.compare(fieldValue, "$eq", condition) {
				return false
			}
		}
	}
	return true
}

// compare performs a comparison based on the operator.
func (s *InMemoryStore) compare(fieldValue interface{}, operator string, opValue interface{}) bool {
	switch operator {
	case "$eq":
		return s.equal(fieldValue, opValue)
	case "$ne":
		return !s.equal(fieldValue, opValue)
	case "$gt":
		return s.greaterThan(fieldValue, opValue)
	case "$gte":
		return s.greaterThan(fieldValue, opValue) || s.equal(fieldValue, opValue)
	case "$lt":
		return s.lessThan(fieldValue, opValue)
	case "$lte":
		return s.lessThan(fieldValue, opValue) || s.equal(fieldValue, opValue)
	case "$in":
		return s.inArray(fieldValue, opValue)
	case "$nin":
		return !s.inArray(fieldValue, opValue)
	case "$regex":
		return s.regexMatch(fieldValue, opValue)
	default:
		return false
	}
}

// equal checks if two values are equal.
func (s *InMemoryStore) equal(a, b interface{}) bool {
	return a == b
}

// greaterThan checks if a > b (supports numeric types).
func (s *InMemoryStore) greaterThan(a, b interface{}) bool {
	switch av := a.(type) {
	case int:
		if bv, ok := b.(int); ok {
			return av > bv
		}
	case int64:
		if bv, ok := b.(int64); ok {
			return av > bv
		}
	case float64:
		if bv, ok := b.(float64); ok {
			return av > bv
		}
	case string:
		if bv, ok := b.(string); ok {
			return av > bv
		}
	}
	return false
}

// lessThan checks if a < b (supports numeric types).
func (s *InMemoryStore) lessThan(a, b interface{}) bool {
	switch av := a.(type) {
	case int:
		if bv, ok := b.(int); ok {
			return av < bv
		}
	case int64:
		if bv, ok := b.(int64); ok {
			return av < bv
		}
	case float64:
		if bv, ok := b.(float64); ok {
			return av < bv
		}
	case string:
		if bv, ok := b.(string); ok {
			return av < bv
		}
	}
	return false
}

// inArray checks if a value is in an array.
func (s *InMemoryStore) inArray(value interface{}, array interface{}) bool {
	arr, ok := array.([]interface{})
	if !ok {
		return false
	}
	for _, v := range arr {
		if s.equal(value, v) {
			return true
		}
	}
	return false
}

// regexMatch checks if a string matches a regex pattern.
func (s *InMemoryStore) regexMatch(value interface{}, pattern interface{}) bool {
	str, ok := value.(string)
	if !ok {
		return false
	}
	patternStr, ok := pattern.(string)
	if !ok {
		return false
	}
	// Compile regex pattern
	re, err := regexp.Compile(patternStr)
	if err != nil {
		// If pattern is invalid, treat as no match
		return false
	}
	return re.MatchString(str)
}

// StoreError represents a store error.
type StoreError struct {
	Message string
	Code    string
}

func (e *StoreError) Error() string {
	if e.Code != "" {
		return e.Code + ": " + e.Message
	}
	return e.Message
}

// cleanupExpired periodically removes expired TTL entries.
func (s *InMemoryStore) cleanupExpired() {
	for {
		select {
		case <-s.cleanupTicker.C:
			s.mu.Lock()
			now := time.Now()
			for fullKey, expiry := range s.ttl {
				if now.After(expiry) {
					// Parse fullKey to get namespace and key
					parts := strings.Split(fullKey, ":")
					if len(parts) == 2 {
						nsKey, key := parts[0], parts[1]
						if nsData, ok := s.data[nsKey]; ok {
							delete(nsData, key)
							if len(nsData) == 0 {
								delete(s.data, nsKey)
							}
						}
					}
					delete(s.ttl, fullKey)
					// Also clean up indexes
					for nsKey := range s.indexes {
						if idxMap, ok := s.indexes[nsKey]; ok {
							delete(idxMap, fullKey)
							if len(idxMap) == 0 {
								delete(s.indexes, nsKey)
							}
						}
					}
				}
			}
			s.mu.Unlock()
		case <-s.stopCleanup:
			return
		}
	}
}

// cosineSimilarity calculates cosine similarity between two vectors.
func cosineSimilarity(a, b []float64) float64 {
	if len(a) != len(b) || len(a) == 0 {
		return 0.0
	}
	var dot, normA, normB float64
	for i := 0; i < len(a); i++ {
		dot += a[i] * b[i]
		normA += a[i] * a[i]
		normB += b[i] * b[i]
	}
	if normA == 0 || normB == 0 {
		return 0.0
	}
	return dot / (sqrt(normA) * sqrt(normB))
}

// sqrt is a simple square root implementation using math.Sqrt.
func sqrt(x float64) float64 {
	return math.Sqrt(x)
}

// searchByEmbedding performs semantic search using embedding vectors.
func (s *InMemoryStore) searchByEmbedding(nsKey string, queryEmbedding []float64, limit int) []*SearchItem {
	if idxMap, ok := s.indexes[nsKey]; ok {
		type scoredItem struct {
			item  *SearchItem
			score float64
		}
		scored := make([]scoredItem, 0, len(idxMap))
		for key, embedding := range idxMap {
			score := cosineSimilarity(queryEmbedding, embedding)
			// Get the corresponding item
			if nsData, ok := s.data[nsKey]; ok {
				if value, ok := nsData[key]; ok {
					fullKey := s.fullKey(nsKey, key)
					if !s.checkTTL(fullKey) {
						continue
					}
					now := time.Now()
					item := &Item{
						Value:     s.copyValue(value),
						Key:       key,
						Namespace: strings.Split(nsKey, "|"),
						CreatedAt: now,
						UpdatedAt: now,
					}
					if expiry, ok := s.ttl[fullKey]; ok {
						item.ExpiresAt = &expiry
					}
					searchItem := &SearchItem{
						Item:  item,
						Score: &score,
					}
					scored = append(scored, scoredItem{searchItem, score})
				}
			}
		}
		// Sort by score descending
		sort.Slice(scored, func(i, j int) bool {
			return scored[i].score > scored[j].score
		})
		// Apply limit
		if limit > 0 && limit < len(scored) {
			scored = scored[:limit]
		}
		// Extract search items
		result := make([]*SearchItem, len(scored))
		for i, s := range scored {
			result[i] = s.item
		}
		return result
	}
	return nil
}
