package cache

import (
	"context"
	"fmt"
	"time"

	"github.com/google/uuid"
)

// SessionData represents cached session information
type SessionData struct {
	SessionID    string    `json:"sessionId"`
	UserID       string    `json:"userId"`
	RefreshToken string    `json:"refreshToken"`
	UserAgent    string    `json:"userAgent"`
	IP           string    `json:"ip"`
	ExpiresAt    time.Time `json:"expiresAt"`
	CreatedAt    time.Time `json:"createdAt"`
}

// SessionCache provides session caching operations
type SessionCache struct {
	service *Service
	keys    *KeyBuilder
}

// NewSessionCache creates a new session cache
func NewSessionCache(service *Service, namespace string) *SessionCache {
	return &SessionCache{
		service: service,
		keys:    NewKeyBuilder(namespace),
	}
}

// SetSession stores session data in cache
func (sc *SessionCache) SetSession(ctx context.Context, session SessionData) error {
	key := sc.keys.SessionKey(session.SessionID)
	ttl := time.Until(session.ExpiresAt)

	if ttl <= 0 {
		return fmt.Errorf("session already expired")
	}

	return sc.service.Set(ctx, key, session, ttl)
}

// GetSession retrieves session data from cache
func (sc *SessionCache) GetSession(ctx context.Context, sessionID string) (*SessionData, error) {
	key := sc.keys.SessionKey(sessionID)
	var session SessionData

	if err := sc.service.Get(ctx, key, &session); err != nil {
		return nil, err
	}

	return &session, nil
}

// DeleteSession removes session data from cache
func (sc *SessionCache) DeleteSession(ctx context.Context, sessionID string) error {
	key := sc.keys.SessionKey(sessionID)
	return sc.service.Delete(ctx, key)
}

// GetSessionByRefreshToken retrieves session by refresh token
// Note: This requires scanning, which is slower. Consider using a secondary index.
func (sc *SessionCache) GetSessionByRefreshToken(ctx context.Context, refreshToken string) (*SessionData, error) {
	pattern := sc.keys.Build(PrefixSession, "*")
	keys, err := sc.service.Keys(ctx, pattern)
	if err != nil {
		return nil, err
	}

	for _, key := range keys {
		var session SessionData
		if err := sc.service.Get(ctx, key, &session); err != nil {
			continue
		}

		if session.RefreshToken == refreshToken {
			return &session, nil
		}
	}

	return nil, ErrCacheMiss
}

// ExtendSession extends the TTL of a session
func (sc *SessionCache) ExtendSession(ctx context.Context, sessionID string, duration time.Duration) error {
	key := sc.keys.SessionKey(sessionID)
	return sc.service.Expire(ctx, key, duration)
}

// UserData represents cached user information
type UserData struct {
	ID          string    `json:"id"`
	Email       string    `json:"email"`
	DisplayName string    `json:"displayName"`
	Role        string    `json:"role"`
	CachedAt    time.Time `json:"cachedAt"`
}

// SetUser stores user data in cache
func (sc *SessionCache) SetUser(ctx context.Context, user UserData) error {
	key := sc.keys.UserKey(user.ID)
	return sc.service.Set(ctx, key, user, TTLUser)
}

// GetUser retrieves user data from cache
func (sc *SessionCache) GetUser(ctx context.Context, userID string) (*UserData, error) {
	key := sc.keys.UserKey(userID)
	var user UserData

	if err := sc.service.Get(ctx, key, &user); err != nil {
		return nil, err
	}

	return &user, nil
}

// DeleteUser removes user data from cache
func (sc *SessionCache) DeleteUser(ctx context.Context, userID string) error {
	key := sc.keys.UserKey(userID)
	return sc.service.Delete(ctx, key)
}

// InvalidateUserSessions removes all sessions for a user
func (sc *SessionCache) InvalidateUserSessions(ctx context.Context, userID string) error {
	pattern := sc.keys.Build(PrefixSession, "*")
	keys, err := sc.service.Keys(ctx, pattern)
	if err != nil {
		return err
	}

	toDelete := make([]string, 0)
	for _, key := range keys {
		var session SessionData
		if err := sc.service.Get(ctx, key, &session); err != nil {
			continue
		}

		if session.UserID == userID {
			toDelete = append(toDelete, key)
		}
	}

	if len(toDelete) > 0 {
		return sc.service.Delete(ctx, toDelete...)
	}

	return nil
}

// RateLimitCheck checks if an action is rate limited
func (sc *SessionCache) RateLimitCheck(ctx context.Context, identifier, action string, limit int64, window time.Duration) (bool, error) {
	key := sc.keys.RateLimitKey(identifier, action)

	count, err := sc.service.Increment(ctx, key)
	if err != nil {
		return false, err
	}

	// Set expiry on first increment
	if count == 1 {
		if err := sc.service.Expire(ctx, key, window); err != nil {
			return false, err
		}
	}

	return count <= limit, nil
}

// AcquireLock attempts to acquire a distributed lock
func (sc *SessionCache) AcquireLock(ctx context.Context, resource string, ttl time.Duration) (string, bool, error) {
	lockID := uuid.New().String()
	key := sc.keys.LockKey(resource)

	acquired, err := sc.service.SetNX(ctx, key, lockID, ttl)
	if err != nil {
		return "", false, err
	}

	return lockID, acquired, nil
}

// ReleaseLock releases a distributed lock
func (sc *SessionCache) ReleaseLock(ctx context.Context, resource, lockID string) error {
	key := sc.keys.LockKey(resource)

	// Verify we own the lock before deleting
	var storedLockID string
	if err := sc.service.Get(ctx, key, &storedLockID); err != nil {
		if err == ErrCacheMiss {
			return nil // Lock already released
		}
		return err
	}

	if storedLockID != lockID {
		return fmt.Errorf("lock owned by different process")
	}

	return sc.service.Delete(ctx, key)
}
