package redis

import (
	"context"
	"encoding/json"
	"time"

	"github.com/redis/go-redis/v9"
	"github.com/rwadurian/mpc-system/services/account/application/ports"
)

// CacheAdapter implements CacheService using Redis
type CacheAdapter struct {
	client *redis.Client
}

// NewCacheAdapter creates a new CacheAdapter
func NewCacheAdapter(client *redis.Client) ports.CacheService {
	return &CacheAdapter{client: client}
}

// Set sets a value in the cache
func (c *CacheAdapter) Set(ctx context.Context, key string, value interface{}, ttlSeconds int) error {
	data, err := json.Marshal(value)
	if err != nil {
		return err
	}

	return c.client.Set(ctx, key, data, time.Duration(ttlSeconds)*time.Second).Err()
}

// Get gets a value from the cache
func (c *CacheAdapter) Get(ctx context.Context, key string) (interface{}, error) {
	data, err := c.client.Get(ctx, key).Bytes()
	if err != nil {
		if err == redis.Nil {
			return nil, nil
		}
		return nil, err
	}

	var value interface{}
	if err := json.Unmarshal(data, &value); err != nil {
		return nil, err
	}

	return value, nil
}

// Delete deletes a value from the cache
func (c *CacheAdapter) Delete(ctx context.Context, key string) error {
	return c.client.Del(ctx, key).Err()
}

// Exists checks if a key exists in the cache
func (c *CacheAdapter) Exists(ctx context.Context, key string) (bool, error) {
	result, err := c.client.Exists(ctx, key).Result()
	if err != nil {
		return false, err
	}
	return result > 0, nil
}

// AccountCacheAdapter provides account-specific caching
type AccountCacheAdapter struct {
	client    *redis.Client
	keyPrefix string
}

// NewAccountCacheAdapter creates a new AccountCacheAdapter
func NewAccountCacheAdapter(client *redis.Client) *AccountCacheAdapter {
	return &AccountCacheAdapter{
		client:    client,
		keyPrefix: "account:",
	}
}

// CacheAccount caches an account
func (c *AccountCacheAdapter) CacheAccount(ctx context.Context, accountID string, data interface{}, ttl time.Duration) error {
	key := c.keyPrefix + accountID
	jsonData, err := json.Marshal(data)
	if err != nil {
		return err
	}
	return c.client.Set(ctx, key, jsonData, ttl).Err()
}

// GetCachedAccount gets a cached account
func (c *AccountCacheAdapter) GetCachedAccount(ctx context.Context, accountID string) (map[string]interface{}, error) {
	key := c.keyPrefix + accountID
	data, err := c.client.Get(ctx, key).Bytes()
	if err != nil {
		if err == redis.Nil {
			return nil, nil
		}
		return nil, err
	}

	var result map[string]interface{}
	if err := json.Unmarshal(data, &result); err != nil {
		return nil, err
	}

	return result, nil
}

// InvalidateAccount invalidates cached account data
func (c *AccountCacheAdapter) InvalidateAccount(ctx context.Context, accountID string) error {
	key := c.keyPrefix + accountID
	return c.client.Del(ctx, key).Err()
}

// CacheLoginChallenge caches a login challenge
func (c *AccountCacheAdapter) CacheLoginChallenge(ctx context.Context, challengeID string, data map[string]interface{}) error {
	key := "login_challenge:" + challengeID
	jsonData, err := json.Marshal(data)
	if err != nil {
		return err
	}
	return c.client.Set(ctx, key, jsonData, 5*time.Minute).Err()
}

// GetLoginChallenge gets a login challenge
func (c *AccountCacheAdapter) GetLoginChallenge(ctx context.Context, challengeID string) (map[string]interface{}, error) {
	key := "login_challenge:" + challengeID
	data, err := c.client.Get(ctx, key).Bytes()
	if err != nil {
		if err == redis.Nil {
			return nil, nil
		}
		return nil, err
	}

	var result map[string]interface{}
	if err := json.Unmarshal(data, &result); err != nil {
		return nil, err
	}

	return result, nil
}

// DeleteLoginChallenge deletes a login challenge after use
func (c *AccountCacheAdapter) DeleteLoginChallenge(ctx context.Context, challengeID string) error {
	key := "login_challenge:" + challengeID
	return c.client.Del(ctx, key).Err()
}

// IncrementLoginAttempts increments failed login attempts
func (c *AccountCacheAdapter) IncrementLoginAttempts(ctx context.Context, username string) (int64, error) {
	key := "login_attempts:" + username
	count, err := c.client.Incr(ctx, key).Result()
	if err != nil {
		return 0, err
	}

	// Set expiry on first attempt
	if count == 1 {
		c.client.Expire(ctx, key, 15*time.Minute)
	}

	return count, nil
}

// GetLoginAttempts gets the current login attempt count
func (c *AccountCacheAdapter) GetLoginAttempts(ctx context.Context, username string) (int64, error) {
	key := "login_attempts:" + username
	count, err := c.client.Get(ctx, key).Int64()
	if err != nil {
		if err == redis.Nil {
			return 0, nil
		}
		return 0, err
	}
	return count, nil
}

// ResetLoginAttempts resets login attempts after successful login
func (c *AccountCacheAdapter) ResetLoginAttempts(ctx context.Context, username string) error {
	key := "login_attempts:" + username
	return c.client.Del(ctx, key).Err()
}
