package contracttest

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

	"github.com/nexuscrm/tests/contract/internal/liveapi"
	"github.com/nexuscrm/tests/contract/internal/providerbootstrap"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
)

var salesforceIDPattern = regexp.MustCompile(`^[a-zA-Z0-9]{15}([a-zA-Z0-9]{3})?$`)
var uuidPattern = regexp.MustCompile(`^[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}$`)

func EnsureLocalRESTBaseline(t testing.TB) {
	t.Helper()

	cfg := liveapi.LoadConfig(t)
	if cfg.TargetName != "local" {
		return
	}

	client := liveapi.NewAuthenticatedClient(t)
	if resp, err := client.DoJSON(http.MethodGet, "/sobjects/Account/describe", nil); err == nil {
		if resp.StatusCode == http.StatusOK {
			_ = resp.Body.Close()
			return
		}
		_ = resp.Body.Close()
	}

	require.NoError(t, providerbootstrap.EnsureLocalBaseline(
		cfg.RootBaseURL,
		cfg.TokenURL,
		cfg.ClientID,
		cfg.ClientSecret,
		cfg.APIVersion,
	))
}

func DoAnonymousRequest(t testing.TB, method, path string, body any) *http.Response {
	t.Helper()

	cfg := liveapi.LoadConfig(t)
	var payload io.Reader
	if body != nil {
		raw, err := json.Marshal(body)
		require.NoError(t, err)
		payload = bytes.NewReader(raw)
	}

	url := strings.TrimRight(cfg.APIBaseURL, "/") + path
	if strings.HasPrefix(path, "/services/") {
		url = strings.TrimRight(cfg.RootBaseURL, "/") + path
	}

	req, err := http.NewRequest(method, url, payload)
	require.NoError(t, err)
	req.Header.Set("Accept", "application/json")
	if body != nil {
		req.Header.Set("Content-Type", "application/json")
	}

	resp, err := (&http.Client{Timeout: 30 * time.Second}).Do(req)
	require.NoError(t, err)
	return resp
}

func AssertSalesforceError(t testing.TB, resp *http.Response, status int, code string) {
	t.Helper()

	require.Equal(t, status, resp.StatusCode)
	body := liveapi.ReadBody(t, resp)

	var arrayPayload []map[string]any
	if err := json.Unmarshal(body, &arrayPayload); err == nil && len(arrayPayload) > 0 {
		assert.Equal(t, code, arrayPayload[0]["errorCode"])
		assert.NotEmpty(t, arrayPayload[0]["message"])
		return
	}

	var objectPayload map[string]any
	require.NoError(t, json.Unmarshal(body, &objectPayload))
	assert.Equal(t, code, objectPayload["errorCode"])
	assert.NotEmpty(t, objectPayload["message"])
}

func AssertSalesforceErrorFields(t testing.TB, resp *http.Response, status int, code string, fields ...string) {
	t.Helper()

	require.Equal(t, status, resp.StatusCode)
	body := liveapi.ReadBody(t, resp)

	var arrayPayload []map[string]any
	if err := json.Unmarshal(body, &arrayPayload); err == nil && len(arrayPayload) > 0 {
		assert.Equal(t, code, arrayPayload[0]["errorCode"])
		assert.NotEmpty(t, arrayPayload[0]["message"])
		assertFieldsMatch(t, arrayPayload[0]["fields"], fields)
		return
	}

	var objectPayload map[string]any
	require.NoError(t, json.Unmarshal(body, &objectPayload))
	assert.Equal(t, code, objectPayload["errorCode"])
	assert.NotEmpty(t, objectPayload["message"])
	assertFieldsMatch(t, objectPayload["fields"], fields)
}

func AssertSalesforceID(t testing.TB, id string) {
	t.Helper()

	require.NotEmpty(t, id)
	if salesforceIDPattern.MatchString(id) || uuidPattern.MatchString(id) {
		return
	}
	assert.Failf(t, "invalid id format", "expected %q to match Salesforce ID or UUID pattern", id)
}

func AssertJSONContentType(t testing.TB, resp *http.Response) {
	t.Helper()

	assert.Contains(t, resp.Header.Get("Content-Type"), "application/json")
}

func assertFieldsMatch(t testing.TB, raw any, fields []string) {
	t.Helper()

	if len(fields) == 0 {
		return
	}
	values, ok := raw.([]any)
	if !ok || len(values) == 0 {
		return
	}
	seen := make([]string, 0, len(values))
	for _, value := range values {
		if field, ok := value.(string); ok {
			seen = append(seen, field)
		}
	}
	assert.ElementsMatch(t, fields, seen)
}
