package infra

import (
	"encoding/json"
	"fmt"
	"os"
	"sync"
	"testing"
	"time"
)

func tempDB(t *testing.T) string {
	t.Helper()
	f, err := os.CreateTemp(t.TempDir(), "cache_*.db")
	if err != nil {
		t.Fatal(err)
	}
	f.Close()
	return f.Name()
}

func TestCacheToSQLite_SetGet(t *testing.T) {
	t.Run("Set/Get basico", func(t *testing.T) {
		c, err := CacheToSQLite(tempDB(t), "default")
		if err != nil {
			t.Fatal(err)
		}
		defer c.Close()

		payload, _ := json.Marshal(map[string]int{"x": 1})
		if err := c.Set("foo", payload, 0); err != nil {
			t.Fatal(err)
		}
		got, ok := c.Get("foo")
		if !ok {
			t.Fatal("expected cache hit")
		}
		var result map[string]int
		json.Unmarshal(got, &result)
		if result["x"] != 1 {
			t.Errorf("got %v, want x=1", result)
		}
	})
}

func TestCacheToSQLite_TTLExpirado(t *testing.T) {
	t.Run("TTL expirado", func(t *testing.T) {
		c, err := CacheToSQLite(tempDB(t), "default")
		if err != nil {
			t.Fatal(err)
		}
		defer c.Close()

		payload, _ := json.Marshal("hello")
		c.Set("temp", payload, 50*time.Millisecond)
		time.Sleep(100 * time.Millisecond)

		_, ok := c.Get("temp")
		if ok {
			t.Error("expected cache miss after TTL expiry")
		}
	})
}

func TestCacheToSQLite_GetOrSet(t *testing.T) {
	t.Run("GetOrSet con factory", func(t *testing.T) {
		c, err := CacheToSQLite(tempDB(t), "default")
		if err != nil {
			t.Fatal(err)
		}
		defer c.Close()

		calls := 0
		factory := func() ([]byte, error) {
			calls++
			return json.Marshal("computed")
		}

		v1, err := c.GetOrSet("k", factory, time.Minute)
		if err != nil {
			t.Fatal(err)
		}
		v2, err := c.GetOrSet("k", factory, time.Minute)
		if err != nil {
			t.Fatal(err)
		}
		if string(v1) != string(v2) {
			t.Errorf("v1=%s v2=%s, want equal", v1, v2)
		}
		if calls != 1 {
			t.Errorf("factory called %d times, want 1", calls)
		}
	})
}

func TestCacheToSQLite_Concurrencia(t *testing.T) {
	t.Run("Concurrencia (goroutines)", func(t *testing.T) {
		c, err := CacheToSQLite(tempDB(t), "parallel")
		if err != nil {
			t.Fatal(err)
		}
		defer c.Close()

		var wg sync.WaitGroup
		errs := make(chan error, 40)
		for i := 0; i < 20; i++ {
			wg.Add(1)
			go func(n int) {
				defer wg.Done()
				key := fmt.Sprintf("key_%d", n)
				payload, _ := json.Marshal(n)
				if err := c.Set(key, payload, 0); err != nil {
					errs <- err
					return
				}
				got, ok := c.Get(key)
				if !ok {
					errs <- fmt.Errorf("miss for key %s", key)
					return
				}
				var val int
				json.Unmarshal(got, &val)
				if val != n {
					errs <- fmt.Errorf("key %s: got %d want %d", key, val, n)
				}
			}(i)
		}
		wg.Wait()
		close(errs)
		for err := range errs {
			t.Error(err)
		}
	})
}
