package asserts

import (
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"regexp"
	"strings"
	"testing"

	"github.com/stretchr/testify/require"
)

var salesforceIDPattern = regexp.MustCompile(`^[a-zA-Z0-9]{15}([a-zA-Z0-9]{3})?$`)

func DecodeAny(t testing.TB, resp *http.Response) any {
	t.Helper()
	defer resp.Body.Close()

	raw, err := io.ReadAll(resp.Body)
	require.NoError(t, err)

	var out any
	require.NoError(t, json.Unmarshal(raw, &out))
	return out
}

func RequireMap(t testing.TB, value any) map[string]any {
	t.Helper()
	mapped, ok := value.(map[string]any)
	require.True(t, ok, "expected object, got %T", value)
	return mapped
}

func RequireSlice(t testing.TB, value any) []any {
	t.Helper()
	list, ok := value.([]any)
	require.True(t, ok, "expected array, got %T", value)
	return list
}

func RequireMapField(t testing.TB, obj map[string]any, key string) map[string]any {
	t.Helper()
	value, ok := obj[key]
	require.True(t, ok, "missing field %q", key)
	return RequireMap(t, value)
}

func RequireSliceField(t testing.TB, obj map[string]any, key string) []any {
	t.Helper()
	value, ok := obj[key]
	require.True(t, ok, "missing field %q", key)
	return RequireSlice(t, value)
}

func RequireStringField(t testing.TB, obj map[string]any, key string) string {
	t.Helper()
	value, ok := obj[key]
	require.True(t, ok, "missing field %q", key)
	asString, ok := value.(string)
	require.True(t, ok, "field %q expected string, got %T", key, value)
	require.NotEmpty(t, asString, "field %q is empty", key)
	return asString
}

func RequireBoolField(t testing.TB, obj map[string]any, key string) bool {
	t.Helper()
	value, ok := obj[key]
	require.True(t, ok, "missing field %q", key)
	asBool, ok := value.(bool)
	require.True(t, ok, "field %q expected bool, got %T", key, value)
	return asBool
}

func RequireNumberField(t testing.TB, obj map[string]any, key string) float64 {
	t.Helper()
	value, ok := obj[key]
	require.True(t, ok, "missing field %q", key)
	asNumber, ok := value.(float64)
	require.True(t, ok, "field %q expected number, got %T", key, value)
	return asNumber
}

func RequireAnyField(t testing.TB, obj map[string]any, keys ...string) (string, any) {
	t.Helper()
	for _, key := range keys {
		if value, ok := obj[key]; ok {
			return key, value
		}
	}
	require.Fail(t, fmt.Sprintf("missing any of fields %v", keys))
	return "", nil
}

func RequireNonEmptySlice(t testing.TB, list []any, label string) {
	t.Helper()
	require.NotEmpty(t, list, "%s should not be empty", label)
}

func RequireNonEmptyMap(t testing.TB, obj map[string]any, label string) {
	t.Helper()
	require.NotEmpty(t, obj, "%s should not be empty", label)
}

func RequireStringFieldAny(t testing.TB, obj map[string]any, keys ...string) string {
	t.Helper()
	key, value := RequireAnyField(t, obj, keys...)
	asString, ok := value.(string)
	require.True(t, ok, "field %q expected string, got %T", key, value)
	require.NotEmpty(t, strings.TrimSpace(asString), "field %q is empty", key)
	return asString
}

func RequireMapFieldAny(t testing.TB, obj map[string]any, keys ...string) map[string]any {
	t.Helper()
	key, value := RequireAnyField(t, obj, keys...)
	mapped, ok := value.(map[string]any)
	require.True(t, ok, "field %q expected object, got %T", key, value)
	require.NotEmpty(t, mapped, "field %q is empty", key)
	return mapped
}

func RequireSliceFieldAny(t testing.TB, obj map[string]any, keys ...string) []any {
	t.Helper()
	key, value := RequireAnyField(t, obj, keys...)
	list, ok := value.([]any)
	require.True(t, ok, "field %q expected array, got %T", key, value)
	require.NotEmpty(t, list, "field %q is empty", key)
	return list
}

func RequireSalesforceID(t testing.TB, id string) string {
	t.Helper()
	id = strings.TrimSpace(id)
	require.NotEmpty(t, id)
	require.Truef(t, salesforceIDPattern.MatchString(id), "expected %q to match Salesforce ID pattern", id)
	return id
}

func RequireSalesforceIDFieldAny(t testing.TB, obj map[string]any, keys ...string) string {
	t.Helper()
	id := RequireStringFieldAny(t, obj, keys...)
	return RequireSalesforceID(t, id)
}

func RequireStringIfPresent(t testing.TB, obj map[string]any, key string) {
	t.Helper()
	value, ok := obj[key]
	if !ok || value == nil {
		return
	}
	_, ok = value.(string)
	require.True(t, ok, "field %q expected string, got %T", key, value)
}

func RequireBoolIfPresent(t testing.TB, obj map[string]any, key string) {
	t.Helper()
	value, ok := obj[key]
	if !ok || value == nil {
		return
	}
	_, ok = value.(bool)
	require.True(t, ok, "field %q expected bool, got %T", key, value)
}

func RequireNumberIfPresent(t testing.TB, obj map[string]any, key string) {
	t.Helper()
	value, ok := obj[key]
	if !ok || value == nil {
		return
	}
	_, ok = value.(float64)
	require.True(t, ok, "field %q expected number, got %T", key, value)
}

func RequireMapIfPresent(t testing.TB, obj map[string]any, key string) {
	t.Helper()
	value, ok := obj[key]
	if !ok || value == nil {
		return
	}
	_, ok = value.(map[string]any)
	require.True(t, ok, "field %q expected object, got %T", key, value)
}

func RequireSliceIfPresent(t testing.TB, obj map[string]any, key string) {
	t.Helper()
	value, ok := obj[key]
	if !ok || value == nil {
		return
	}
	_, ok = value.([]any)
	require.True(t, ok, "field %q expected array, got %T", key, value)
}
