//go:build e2e

package shared_test

import (
	"context"
	"fmt"
	"net/http"
	"testing"
	"time"

	e2e "github.com/DeBrosOfficial/network/e2e"
)

func TestCache_Health(t *testing.T) {
	e2e.SkipIfMissingGateway(t)

	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
	defer cancel()

	req := &e2e.HTTPRequest{
		Method: http.MethodGet,
		URL:    e2e.GetGatewayURL() + "/v1/cache/health",
	}

	body, status, err := req.Do(ctx)
	if err != nil {
		t.Fatalf("health check failed: %v", err)
	}

	if status != http.StatusOK {
		t.Fatalf("expected status 200, got %d", status)
	}

	var resp map[string]interface{}
	if err := e2e.DecodeJSON(body, &resp); err != nil {
		t.Fatalf("failed to decode response: %v", err)
	}

	if resp["status"] != "ok" {
		t.Fatalf("expected status 'ok', got %v", resp["status"])
	}

	if resp["service"] != "olric" {
		t.Fatalf("expected service 'olric', got %v", resp["service"])
	}
}

func TestCache_PutGet(t *testing.T) {
	e2e.SkipIfMissingGateway(t)

	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
	defer cancel()

	dmap := e2e.GenerateDMapName()
	key := "test-key"
	value := "test-value"

	// Put value
	putReq := &e2e.HTTPRequest{
		Method: http.MethodPost,
		URL:    e2e.GetGatewayURL() + "/v1/cache/put",
		Body: map[string]interface{}{
			"dmap":  dmap,
			"key":   key,
			"value": value,
		},
	}

	body, status, err := putReq.Do(ctx)
	if err != nil {
		t.Fatalf("put failed: %v", err)
	}

	if status != http.StatusOK {
		t.Fatalf("expected status 200, got %d: %s", status, string(body))
	}

	// Get value
	getReq := &e2e.HTTPRequest{
		Method: http.MethodPost,
		URL:    e2e.GetGatewayURL() + "/v1/cache/get",
		Body: map[string]interface{}{
			"dmap": dmap,
			"key":  key,
		},
	}

	body, status, err = getReq.Do(ctx)
	if err != nil {
		t.Fatalf("get failed: %v", err)
	}

	if status != http.StatusOK {
		t.Fatalf("expected status 200, got %d", status)
	}

	var getResp map[string]interface{}
	if err := e2e.DecodeJSON(body, &getResp); err != nil {
		t.Fatalf("failed to decode response: %v", err)
	}

	if getResp["value"] != value {
		t.Fatalf("expected value %q, got %v", value, getResp["value"])
	}
}

func TestCache_PutGetJSON(t *testing.T) {
	e2e.SkipIfMissingGateway(t)

	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
	defer cancel()

	dmap := e2e.GenerateDMapName()
	key := "json-key"
	jsonValue := map[string]interface{}{
		"name": "John",
		"age":  30,
		"tags": []string{"developer", "golang"},
	}

	// Put JSON value
	putReq := &e2e.HTTPRequest{
		Method: http.MethodPost,
		URL:    e2e.GetGatewayURL() + "/v1/cache/put",
		Body: map[string]interface{}{
			"dmap":  dmap,
			"key":   key,
			"value": jsonValue,
		},
	}

	_, status, err := putReq.Do(ctx)
	if err != nil {
		t.Fatalf("put failed: %v", err)
	}

	if status != http.StatusOK {
		t.Fatalf("expected status 200, got %d", status)
	}

	// Get JSON value
	getReq := &e2e.HTTPRequest{
		Method: http.MethodPost,
		URL:    e2e.GetGatewayURL() + "/v1/cache/get",
		Body: map[string]interface{}{
			"dmap": dmap,
			"key":  key,
		},
	}

	body, status, err := getReq.Do(ctx)
	if err != nil {
		t.Fatalf("get failed: %v", err)
	}

	if status != http.StatusOK {
		t.Fatalf("expected status 200, got %d", status)
	}

	var getResp map[string]interface{}
	if err := e2e.DecodeJSON(body, &getResp); err != nil {
		t.Fatalf("failed to decode response: %v", err)
	}

	retrievedValue := getResp["value"].(map[string]interface{})
	if retrievedValue["name"] != jsonValue["name"] {
		t.Fatalf("expected name %q, got %v", jsonValue["name"], retrievedValue["name"])
	}
	if retrievedValue["age"] != float64(30) {
		t.Fatalf("expected age 30, got %v", retrievedValue["age"])
	}
}

func TestCache_Delete(t *testing.T) {
	e2e.SkipIfMissingGateway(t)

	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
	defer cancel()

	dmap := e2e.GenerateDMapName()
	key := "delete-key"
	value := "delete-value"

	// Put value
	putReq := &e2e.HTTPRequest{
		Method: http.MethodPost,
		URL:    e2e.GetGatewayURL() + "/v1/cache/put",
		Body: map[string]interface{}{
			"dmap":  dmap,
			"key":   key,
			"value": value,
		},
	}

	_, status, err := putReq.Do(ctx)
	if err != nil || status != http.StatusOK {
		t.Fatalf("put failed: status %d, err %v", status, err)
	}

	// Delete value
	deleteReq := &e2e.HTTPRequest{
		Method: http.MethodPost,
		URL:    e2e.GetGatewayURL() + "/v1/cache/delete",
		Body: map[string]interface{}{
			"dmap": dmap,
			"key":  key,
		},
	}

	_, status, err = deleteReq.Do(ctx)
	if err != nil {
		t.Fatalf("delete failed: %v", err)
	}

	if status != http.StatusOK {
		t.Fatalf("expected status 200, got %d", status)
	}

	// Verify deletion
	getReq := &e2e.HTTPRequest{
		Method: http.MethodPost,
		URL:    e2e.GetGatewayURL() + "/v1/cache/get",
		Body: map[string]interface{}{
			"dmap": dmap,
			"key":  key,
		},
	}

	_, status, err = getReq.Do(ctx)
	// Should get 404 for missing key
	if status != http.StatusNotFound {
		t.Fatalf("expected status 404 for deleted key, got %d", status)
	}
}

func TestCache_TTL(t *testing.T) {
	e2e.SkipIfMissingGateway(t)

	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
	defer cancel()

	dmap := e2e.GenerateDMapName()
	key := "ttl-key"
	value := "ttl-value"

	// Put value with TTL
	putReq := &e2e.HTTPRequest{
		Method: http.MethodPost,
		URL:    e2e.GetGatewayURL() + "/v1/cache/put",
		Body: map[string]interface{}{
			"dmap":  dmap,
			"key":   key,
			"value": value,
			"ttl":   "2s",
		},
	}

	_, status, err := putReq.Do(ctx)
	if err != nil {
		t.Fatalf("put with TTL failed: %v", err)
	}

	if status != http.StatusOK {
		t.Fatalf("expected status 200, got %d", status)
	}

	// Verify value exists
	getReq := &e2e.HTTPRequest{
		Method: http.MethodPost,
		URL:    e2e.GetGatewayURL() + "/v1/cache/get",
		Body: map[string]interface{}{
			"dmap": dmap,
			"key":  key,
		},
	}

	_, status, err = getReq.Do(ctx)
	if err != nil || status != http.StatusOK {
		t.Fatalf("get immediately after put failed: status %d, err %v", status, err)
	}

	// Wait for TTL expiry (2 seconds + buffer)
	e2e.Delay(2500)

	// Verify value is expired
	_, status, err = getReq.Do(ctx)
	if status != http.StatusNotFound {
		t.Logf("warning: TTL expiry may not be fully implemented; got status %d", status)
	}
}

func TestCache_Scan(t *testing.T) {
	e2e.SkipIfMissingGateway(t)

	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
	defer cancel()

	dmap := e2e.GenerateDMapName()

	// Put multiple keys
	keys := []string{"user-1", "user-2", "session-1", "session-2"}
	for _, key := range keys {
		putReq := &e2e.HTTPRequest{
			Method: http.MethodPost,
			URL:    e2e.GetGatewayURL() + "/v1/cache/put",
			Body: map[string]interface{}{
				"dmap":  dmap,
				"key":   key,
				"value": "value-" + key,
			},
		}

		_, status, err := putReq.Do(ctx)
		if err != nil || status != http.StatusOK {
			t.Fatalf("put failed: status %d, err %v", status, err)
		}
	}

	// Scan all keys
	scanReq := &e2e.HTTPRequest{
		Method: http.MethodPost,
		URL:    e2e.GetGatewayURL() + "/v1/cache/scan",
		Body: map[string]interface{}{
			"dmap": dmap,
		},
	}

	body, status, err := scanReq.Do(ctx)
	if err != nil {
		t.Fatalf("scan failed: %v", err)
	}

	if status != http.StatusOK {
		t.Fatalf("expected status 200, got %d", status)
	}

	var scanResp map[string]interface{}
	if err := e2e.DecodeJSON(body, &scanResp); err != nil {
		t.Fatalf("failed to decode response: %v", err)
	}

	keysResp := scanResp["keys"].([]interface{})
	if len(keysResp) < 4 {
		t.Fatalf("expected at least 4 keys, got %d", len(keysResp))
	}
}

func TestCache_ScanWithRegex(t *testing.T) {
	e2e.SkipIfMissingGateway(t)

	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
	defer cancel()

	dmap := e2e.GenerateDMapName()

	// Put keys with different patterns
	keys := []string{"user-1", "user-2", "session-1", "session-2"}
	for _, key := range keys {
		putReq := &e2e.HTTPRequest{
			Method: http.MethodPost,
			URL:    e2e.GetGatewayURL() + "/v1/cache/put",
			Body: map[string]interface{}{
				"dmap":  dmap,
				"key":   key,
				"value": "value-" + key,
			},
		}

		_, status, err := putReq.Do(ctx)
		if err != nil || status != http.StatusOK {
			t.Fatalf("put failed: status %d, err %v", status, err)
		}
	}

	// Scan with regex pattern
	scanReq := &e2e.HTTPRequest{
		Method: http.MethodPost,
		URL:    e2e.GetGatewayURL() + "/v1/cache/scan",
		Body: map[string]interface{}{
			"dmap":    dmap,
			"pattern": "^user-",
		},
	}

	body, status, err := scanReq.Do(ctx)
	if err != nil {
		t.Fatalf("scan with regex failed: %v", err)
	}

	if status != http.StatusOK {
		t.Fatalf("expected status 200, got %d", status)
	}

	var scanResp map[string]interface{}
	if err := e2e.DecodeJSON(body, &scanResp); err != nil {
		t.Fatalf("failed to decode response: %v", err)
	}

	keysResp := scanResp["keys"].([]interface{})
	if len(keysResp) < 2 {
		t.Fatalf("expected at least 2 keys matching pattern, got %d", len(keysResp))
	}
}

func TestCache_MultiGet(t *testing.T) {
	e2e.SkipIfMissingGateway(t)

	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
	defer cancel()

	dmap := e2e.GenerateDMapName()
	keys := []string{"key-1", "key-2", "key-3"}

	// Put values
	for i, key := range keys {
		putReq := &e2e.HTTPRequest{
			Method: http.MethodPost,
			URL:    e2e.GetGatewayURL() + "/v1/cache/put",
			Body: map[string]interface{}{
				"dmap":  dmap,
				"key":   key,
				"value": fmt.Sprintf("value-%d", i),
			},
		}

		_, status, err := putReq.Do(ctx)
		if err != nil || status != http.StatusOK {
			t.Fatalf("put failed: status %d, err %v", status, err)
		}
	}

	// Multi-get
	multiGetReq := &e2e.HTTPRequest{
		Method: http.MethodPost,
		URL:    e2e.GetGatewayURL() + "/v1/cache/mget",
		Body: map[string]interface{}{
			"dmap": dmap,
			"keys": keys,
		},
	}

	body, status, err := multiGetReq.Do(ctx)
	if err != nil {
		t.Fatalf("mget failed: %v", err)
	}

	if status != http.StatusOK {
		t.Fatalf("expected status 200, got %d", status)
	}

	var mgetResp map[string]interface{}
	if err := e2e.DecodeJSON(body, &mgetResp); err != nil {
		t.Fatalf("failed to decode response: %v", err)
	}

	results := mgetResp["results"].([]interface{})
	if len(results) != 3 {
		t.Fatalf("expected 3 results, got %d", len(results))
	}
}

func TestCache_MissingDMap(t *testing.T) {
	e2e.SkipIfMissingGateway(t)

	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
	defer cancel()

	getReq := &e2e.HTTPRequest{
		Method: http.MethodPost,
		URL:    e2e.GetGatewayURL() + "/v1/cache/get",
		Body: map[string]interface{}{
			"dmap": "",
			"key":  "any-key",
		},
	}

	_, status, err := getReq.Do(ctx)
	if err != nil {
		t.Fatalf("request failed: %v", err)
	}

	if status != http.StatusBadRequest {
		t.Fatalf("expected status 400 for missing dmap, got %d", status)
	}
}

func TestCache_MissingKey(t *testing.T) {
	e2e.SkipIfMissingGateway(t)

	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
	defer cancel()

	dmap := e2e.GenerateDMapName()

	getReq := &e2e.HTTPRequest{
		Method: http.MethodPost,
		URL:    e2e.GetGatewayURL() + "/v1/cache/get",
		Body: map[string]interface{}{
			"dmap": dmap,
			"key":  "non-existent-key",
		},
	}

	_, status, err := getReq.Do(ctx)
	if err != nil {
		t.Fatalf("request failed: %v", err)
	}

	if status != http.StatusNotFound {
		t.Fatalf("expected status 404 for missing key, got %d", status)
	}
}
