package service

import (
	"baron-sso-backend/internal/domain"
	"context"
	"encoding/json"
	"os"
	"time"

	"github.com/go-redis/redis/v8"
)

var ctx = context.Background()

type RedisService struct {
	Client *redis.Client
}

type identityMirrorStateStore struct {
	Status          string     `json:"status"`
	LastRefreshedAt *time.Time `json:"lastRefreshedAt,omitempty"`
	LastError       string     `json:"lastError,omitempty"`
	ObservedCount   int64      `json:"observedCount,omitempty"`
	UpdatedAt       *time.Time `json:"updatedAt,omitempty"`
}

// NewRedisService creates and returns a new RedisService
func NewRedisService() (*RedisService, error) {
	redisAddr := os.Getenv("REDIS_ADDR")
	if redisAddr == "" {
		redisAddr = "localhost:6389" // Fallback for local dev without Docker
	}

	rdb := redis.NewClient(&redis.Options{
		Addr: redisAddr,
	})

	// Ping the server to check the connection
	if _, err := rdb.Ping(ctx).Result(); err != nil {
		return nil, err
	}

	// [DEV-FIX] Disable stop-writes-on-bgsave-error to allow writes even if persistence fails
	// This is common in dev docker environments with permission issues.
	rdb.ConfigSet(ctx, "stop-writes-on-bgsave-error", "no")

	return &RedisService{Client: rdb}, nil
}

func (s *RedisService) Ping(ctx context.Context) error {
	if s.Client == nil {
		return os.ErrInvalid
	}
	return s.Client.Ping(ctx).Err()
}

// StoreVerificationCode saves the SMS verification code with a 3-minute expiration
func (s *RedisService) StoreVerificationCode(phone, code string) error {
	// Key format: "sms_verify:01012345678"
	key := "sms_verify:" + phone
	expiration := 3 * time.Minute
	err := s.Client.Set(ctx, key, code, expiration).Err()
	return err
}

// GetVerificationCode retrieves the SMS verification code
func (s *RedisService) GetVerificationCode(phone string) (string, error) {
	key := "sms_verify:" + phone
	code, err := s.Client.Get(ctx, key).Result()
	if err == redis.Nil {
		// Key does not exist (expired or incorrect phone number)
		return "", nil
	} else if err != nil {
		return "", err
	}
	return code, nil
}

// DeleteVerificationCode removes the verification code after successful verification
func (s *RedisService) DeleteVerificationCode(phone string) error {
	key := "sms_verify:" + phone
	return s.Client.Del(ctx, key).Err()
}

// Set stores a key-value pair with expiration
func (s *RedisService) Set(key string, value string, expiration time.Duration) error {
	return s.Client.Set(ctx, key, value, expiration).Err()
}

// Get retrieves a value by key
func (s *RedisService) Get(key string) (string, error) {
	val, err := s.Client.Get(ctx, key).Result()
	if err == redis.Nil {
		return "", nil
	}
	return val, err
}

// Delete removes a key
func (s *RedisService) Delete(key string) error {
	return s.Client.Del(ctx, key).Err()
}

func (s *RedisService) GetIdentityCacheStatus(ctx context.Context) (domain.IdentityCacheStatus, error) {
	if s == nil || s.Client == nil {
		return domain.IdentityCacheStatus{
			Status:     "unavailable",
			RedisReady: false,
			LastError:  "redis service unavailable",
		}, nil
	}

	if err := s.Client.Ping(ctx).Err(); err != nil {
		return domain.IdentityCacheStatus{
			Status:     "failed",
			RedisReady: false,
			LastError:  err.Error(),
		}, nil
	}

	keyCount, err := s.countIdentityCacheKeys(ctx)
	if err != nil {
		return domain.IdentityCacheStatus{
			Status:     "failed",
			RedisReady: true,
			LastError:  err.Error(),
		}, nil
	}

	raw, err := s.Client.Get(ctx, "identity:mirror:state").Result()
	if err == redis.Nil {
		return domain.IdentityCacheStatus{
			Status:     "empty",
			RedisReady: true,
			KeyCount:   keyCount,
		}, nil
	}
	if err != nil {
		return domain.IdentityCacheStatus{
			Status:     "failed",
			RedisReady: true,
			KeyCount:   keyCount,
			LastError:  err.Error(),
		}, nil
	}

	var stored identityMirrorStateStore
	if err := json.Unmarshal([]byte(raw), &stored); err != nil {
		return domain.IdentityCacheStatus{
			Status:     "failed",
			RedisReady: true,
			KeyCount:   keyCount,
			LastError:  err.Error(),
		}, nil
	}
	status := stored.Status
	if status == "" {
		status = "unknown"
	}
	return domain.IdentityCacheStatus{
		Status:          status,
		RedisReady:      true,
		ObservedCount:   stored.ObservedCount,
		KeyCount:        keyCount,
		LastRefreshedAt: stored.LastRefreshedAt,
		LastError:       stored.LastError,
		UpdatedAt:       stored.UpdatedAt,
	}, nil
}

func (s *RedisService) FlushIdentityCache(ctx context.Context) (domain.IdentityCacheFlushResult, error) {
	if s == nil || s.Client == nil {
		return domain.IdentityCacheFlushResult{}, os.ErrInvalid
	}

	keys, err := s.identityCacheKeys(ctx)
	if err != nil {
		return domain.IdentityCacheFlushResult{}, err
	}
	var deleted int64
	for len(keys) > 0 {
		chunkSize := len(keys)
		if chunkSize > 500 {
			chunkSize = 500
		}
		chunk := keys[:chunkSize]
		count, err := s.Client.Del(ctx, chunk...).Result()
		if err != nil {
			return domain.IdentityCacheFlushResult{}, err
		}
		deleted += count
		keys = keys[chunkSize:]
	}

	return domain.IdentityCacheFlushResult{
		Status:      "success",
		FlushedKeys: deleted,
		UpdatedAt:   time.Now().UTC(),
	}, nil
}

func (s *RedisService) countIdentityCacheKeys(ctx context.Context) (int64, error) {
	keys, err := s.identityCacheKeys(ctx)
	if err != nil {
		return 0, err
	}
	return int64(len(keys)), nil
}

func (s *RedisService) identityCacheKeys(ctx context.Context) ([]string, error) {
	seen := make(map[string]bool)
	patterns := []string{
		"identity:mirror:*",
		"identity:index:*",
	}
	for _, pattern := range patterns {
		var cursor uint64
		for {
			keys, next, err := s.Client.Scan(ctx, cursor, pattern, 250).Result()
			if err != nil {
				return nil, err
			}
			for _, key := range keys {
				seen[key] = true
			}
			cursor = next
			if cursor == 0 {
				break
			}
		}
	}

	keys := make([]string, 0, len(seen))
	for key := range seen {
		keys = append(keys, key)
	}
	return keys, nil
}
