//nolint:testpackage // parseThreshold is unexported; internal test package required
package handlers

import (
	"encoding/json"
	"math"
	"testing"
)

//nolint:gocognit
func TestParseThreshold(t *testing.T) {
	t.Parallel()

	t.Run("nil/empty raw message returns nil", func(t *testing.T) {
		t.Parallel()

		val, err := parseThreshold(nil)
		if err != nil {
			t.Fatalf("unexpected error: %v", err)
		}
		if val != nil {
			t.Fatalf("expected nil, got %v", *val)
		}
	})

	t.Run("empty slice returns nil", func(t *testing.T) {
		t.Parallel()

		val, err := parseThreshold(json.RawMessage{})
		if err != nil {
			t.Fatalf("unexpected error: %v", err)
		}
		if val != nil {
			t.Fatalf("expected nil, got %v", *val)
		}
	})

	t.Run("JSON null returns nil", func(t *testing.T) {
		t.Parallel()

		val, err := parseThreshold(json.RawMessage("null"))
		if err != nil {
			t.Fatalf("unexpected error: %v", err)
		}
		if val != nil {
			t.Fatalf("expected nil, got %v", *val)
		}
	})

	t.Run("number 10 returns 10.0", func(t *testing.T) {
		t.Parallel()

		val, err := parseThreshold(json.RawMessage("10"))
		if err != nil {
			t.Fatalf("unexpected error: %v", err)
		}
		if val == nil {
			t.Fatal("expected non-nil value")
		}
		if *val != 10.0 {
			t.Fatalf("expected 10.0, got %v", *val)
		}
	})

	t.Run("number 0.5 returns 0.5", func(t *testing.T) {
		t.Parallel()

		val, err := parseThreshold(json.RawMessage("0.5"))
		if err != nil {
			t.Fatalf("unexpected error: %v", err)
		}
		if val == nil || *val != 0.5 {
			t.Fatalf("expected 0.5, got %v", val)
		}
	})

	t.Run("string '10' returns 10.0", func(t *testing.T) {
		t.Parallel()

		val, err := parseThreshold(json.RawMessage(`"10"`))
		if err != nil {
			t.Fatalf("unexpected error: %v", err)
		}
		if val == nil || *val != 10.0 {
			t.Fatalf("expected 10.0, got %v", val)
		}
	})

	t.Run("string '0.001' returns 0.001", func(t *testing.T) {
		t.Parallel()

		val, err := parseThreshold(json.RawMessage(`"0.001"`))
		if err != nil {
			t.Fatalf("unexpected error: %v", err)
		}
		if val == nil || math.Abs(*val-0.001) > 1e-9 {
			t.Fatalf("expected 0.001, got %v", val)
		}
	})

	t.Run("string with spaces '  10  ' returns 10.0", func(t *testing.T) {
		t.Parallel()

		val, err := parseThreshold(json.RawMessage(`"  10  "`))
		if err != nil {
			t.Fatalf("unexpected error: %v", err)
		}
		if val == nil || *val != 10.0 {
			t.Fatalf("expected 10.0, got %v", val)
		}
	})

	t.Run("empty string returns nil", func(t *testing.T) {
		t.Parallel()

		val, err := parseThreshold(json.RawMessage(`""`))
		if err != nil {
			t.Fatalf("unexpected error: %v", err)
		}
		if val != nil {
			t.Fatalf("expected nil, got %v", *val)
		}
	})

	t.Run("whitespace-only string returns nil", func(t *testing.T) {
		t.Parallel()

		val, err := parseThreshold(json.RawMessage(`"   "`))
		if err != nil {
			t.Fatalf("unexpected error: %v", err)
		}
		if val != nil {
			t.Fatalf("expected nil, got %v", *val)
		}
	})

	t.Run("invalid string returns error", func(t *testing.T) {
		t.Parallel()

		_, err := parseThreshold(json.RawMessage(`"abc"`))
		if err == nil {
			t.Fatal("expected error for non-numeric string")
		}
	})

	t.Run("boolean returns error", func(t *testing.T) {
		t.Parallel()

		_, err := parseThreshold(json.RawMessage("true"))
		if err == nil {
			t.Fatal("expected error for boolean")
		}
	})

	t.Run("array returns error", func(t *testing.T) {
		t.Parallel()

		_, err := parseThreshold(json.RawMessage("[1,2]"))
		if err == nil {
			t.Fatal("expected error for array")
		}
	})
}

//nolint:funlen
func TestDecodeAlertBody(t *testing.T) {
	t.Parallel()

	// Verifies that the struct used in Create handler can decode all
	// payload shapes the frontend might send.

	type alertBody struct {
		Type      string          `json:"type"`
		Threshold json.RawMessage `json:"threshold"`
	}

	tests := []struct {
		name    string
		payload string
		wantErr bool
	}{
		{
			name:    "incoming_tx without threshold",
			payload: `{"type":"incoming_tx"}`,
		},
		{
			name:    "outgoing_tx without threshold",
			payload: `{"type":"outgoing_tx"}`,
		},
		{
			name:    "large_transfer with number threshold",
			payload: `{"type":"large_transfer","threshold":10}`,
		},
		{
			name:    "large_transfer with string threshold",
			payload: `{"type":"large_transfer","threshold":"10"}`,
		},
		{
			name:    "balance_below with number threshold",
			payload: `{"type":"balance_below","threshold":0.5}`,
		},
		{
			name:    "threshold null",
			payload: `{"type":"incoming_tx","threshold":null}`,
		},
		{
			name:    "empty object",
			payload: `{}`,
		},
		{
			name:    "empty body",
			payload: ``,
			wantErr: true,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			t.Parallel()

			var body alertBody
			err := json.Unmarshal([]byte(tt.payload), &body)
			if tt.wantErr {
				if err == nil {
					t.Fatal("expected decode error")
				}

				return
			}
			if err != nil {
				t.Fatalf("unexpected decode error: %v", err)
			}
		})
	}
}

func TestOldStructFailsWithStringThreshold(t *testing.T) {
	t.Parallel()

	// Documents the original bug: *float64 cannot decode a string threshold.
	type oldAlertBody struct {
		Type      string   `json:"type"`
		Threshold *float64 `json:"threshold"`
	}

	payload := `{"type":"large_transfer","threshold":"10"}`
	var body oldAlertBody
	err := json.Unmarshal([]byte(payload), &body)
	if err == nil {
		t.Fatal("expected error: old struct with *float64 should reject string threshold")
	}
}
