package service

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

	"github.com/go-redis/redis/v8"
	"github.com/stretchr/testify/require"
)

type redisCommandStub struct {
	scans      map[string][]string
	stateValue string
	deleted    []string
}

func (h *redisCommandStub) BeforeProcess(ctx context.Context, cmd redis.Cmder) (context.Context, error) {
	return ctx, nil
}

func (h *redisCommandStub) AfterProcess(ctx context.Context, cmd redis.Cmder) error {
	switch cmd.Name() {
	case "ping":
		if status, ok := cmd.(*redis.StatusCmd); ok {
			status.SetVal("PONG")
		}
	case "scan":
		if scan, ok := cmd.(*redis.ScanCmd); ok {
			scan.SetVal(h.scans[scanPattern(cmd.Args())], 0)
		}
	case "get":
		if str, ok := cmd.(*redis.StringCmd); ok {
			if h.stateValue == "" {
				str.SetErr(redis.Nil)
				return nil
			}
			str.SetVal(h.stateValue)
		}
	case "del":
		args := cmd.Args()
		keys := make([]string, 0, len(args)-1)
		for _, arg := range args[1:] {
			keys = append(keys, arg.(string))
		}
		h.deleted = append(h.deleted, keys...)
		if count, ok := cmd.(*redis.IntCmd); ok {
			count.SetVal(int64(len(keys)))
		}
	}
	cmd.SetErr(nil)
	return nil
}

func (h *redisCommandStub) BeforeProcessPipeline(ctx context.Context, cmds []redis.Cmder) (context.Context, error) {
	return ctx, nil
}

func (h *redisCommandStub) AfterProcessPipeline(ctx context.Context, cmds []redis.Cmder) error {
	return nil
}

func scanPattern(args []interface{}) string {
	for index := 0; index < len(args)-1; index++ {
		value, ok := args[index].(string)
		if ok && value == "match" {
			if pattern, ok := args[index+1].(string); ok {
				return pattern
			}
		}
	}
	return ""
}

func newStubbedRedisService(stub *redisCommandStub) *RedisService {
	client := redis.NewClient(&redis.Options{
		Addr:       "127.0.0.1:1",
		MaxRetries: -1,
	})
	client.AddHook(stub)
	return &RedisService{Client: client}
}

func TestRedisServiceGetIdentityCacheStatusReadsStateAndCountsCacheKeys(t *testing.T) {
	now := time.Date(2026, 6, 9, 3, 20, 0, 0, time.UTC)
	state, err := json.Marshal(identityMirrorStateStore{
		Status:          "ready",
		LastRefreshedAt: &now,
		ObservedCount:   42,
		UpdatedAt:       &now,
	})
	require.NoError(t, err)

	stub := &redisCommandStub{
		stateValue: string(state),
		scans: map[string][]string{
			"identity:mirror:*": {"identity:mirror:state", "identity:mirror:user:1"},
			"identity:index:*":  {"identity:index:email:a", "identity:mirror:user:1"},
		},
	}
	service := newStubbedRedisService(stub)

	status, err := service.GetIdentityCacheStatus(context.Background())

	require.NoError(t, err)
	require.Equal(t, "ready", status.Status)
	require.True(t, status.RedisReady)
	require.Equal(t, int64(42), status.ObservedCount)
	require.Equal(t, int64(3), status.KeyCount)
	require.Equal(t, &now, status.LastRefreshedAt)
	require.Equal(t, &now, status.UpdatedAt)
}

func TestRedisServiceFlushIdentityCacheDeletesOnlyIdentityMirrorAndIndexKeys(t *testing.T) {
	stub := &redisCommandStub{
		scans: map[string][]string{
			"identity:mirror:*": {"identity:mirror:state", "identity:mirror:user:1"},
			"identity:index:*":  {"identity:index:email:a", "identity:mirror:user:1"},
		},
	}
	service := newStubbedRedisService(stub)

	result, err := service.FlushIdentityCache(context.Background())

	require.NoError(t, err)
	require.Equal(t, "success", result.Status)
	require.Equal(t, int64(3), result.FlushedKeys)
	require.ElementsMatch(t, []string{
		"identity:mirror:state",
		"identity:mirror:user:1",
		"identity:index:email:a",
	}, stub.deleted)
}

func TestRedisServiceGetIdentityCacheStatusReturnsUnavailableWithoutClient(t *testing.T) {
	status, err := (*RedisService)(nil).GetIdentityCacheStatus(context.Background())

	require.NoError(t, err)
	require.Equal(t, "unavailable", status.Status)
	require.False(t, status.RedisReady)
	require.NotEmpty(t, status.LastError)
}

func TestRedisServiceFlushIdentityCacheFailsWithoutClient(t *testing.T) {
	_, err := (*RedisService)(nil).FlushIdentityCache(context.Background())

	require.ErrorIs(t, err, os.ErrInvalid)
}
