package binding

import (
	"encoding/json"
	"testing"

	"github.com/stretchr/testify/require"

	"github.com/tidbcloud/AgentTeam/internal/extension/registry"
	"github.com/tidbcloud/AgentTeam/internal/extension/spec"
)

func TestBindIsByteDeterministicAndSecretFree(t *testing.T) {
	plan := testActionPlan()
	first, err := Bind(plan, json.RawMessage(
		`{"owner":"pingcap/escape","repo":"agentteam","labels":["bug","p0"],"title":"hello","metadata":{"z":2,"a":1}}`,
	))
	require.NoError(t, err)
	second, err := Bind(plan, json.RawMessage(
		`{"metadata":{"a":1,"z":2},"title":"hello","labels":["bug","p0"],"repo":"agentteam","owner":"pingcap/escape"}`,
	))
	require.NoError(t, err)

	require.Equal(t, "POST", first.Method)
	require.Equal(t,
		"https://api.example.com/v1/repos/pingcap%2Fescape/agentteam/issues?labels=bug&labels=p0",
		first.URL.String(),
	)
	require.Equal(t, "application/json", first.Headers.Get("Content-Type"))
	require.Equal(t, "application/vnd.example+json", first.Headers.Get("Accept"))
	require.JSONEq(t, `{"metadata":{"a":1,"z":2},"title":"hello"}`, string(first.Body))
	require.Equal(t, first.Method, second.Method)
	require.Equal(t, first.URL.String(), second.URL.String())
	require.Equal(t, first.Headers, second.Headers)
	require.Equal(t, first.Body, second.Body)
	require.Equal(t, first.ResponsePolicy, second.ResponsePolicy)
	require.Equal(t, first.SecretFreeHash, second.SecretFreeHash)
	require.NotContains(t, first.SecretFreeHash, "hello")
	require.Regexp(t, `^sha256:[0-9a-f]{64}$`, first.SecretFreeHash)
}

func TestBindRejectsSchemaMismatchAndOversizeInput(t *testing.T) {
	plan := testActionPlan()
	_, err := Bind(plan, json.RawMessage(`{"owner":"pingcap","repo":"agentteam"}`))
	require.ErrorContains(t, err, "validate input")

	oversize := make(json.RawMessage, MaxInputBytes+1)
	for i := range oversize {
		oversize[i] = ' '
	}
	_, err = Bind(plan, oversize)
	require.ErrorIs(t, err, ErrInputTooLarge)
}

func TestBindRejectsReservedHeaderEvenForTamperedPlan(t *testing.T) {
	plan := testActionPlan()
	plan.Binding.StaticHeaders["Authorization"] = "Bearer attacker"
	_, err := Bind(plan, json.RawMessage(
		`{"owner":"pingcap","repo":"agentteam","title":"hello"}`,
	))
	require.ErrorContains(t, err, "reserved header")
}

func testActionPlan() registry.ActionPlan {
	return registry.ActionPlan{
		DefinitionDigest:  "sha256:definition",
		Domain:            "github",
		DefinitionVersion: 1,
		ActionName:        "create-issue",
		Safety:            spec.Safety{Class: "mutation"},
		InputSchema: map[string]any{
			"$schema":              "https://json-schema.org/draft/2020-12/schema",
			"type":                 "object",
			"additionalProperties": false,
			"required":             []any{"owner", "repo", "title"},
			"properties": map[string]any{
				"owner":    map[string]any{"type": "string"},
				"repo":     map[string]any{"type": "string"},
				"title":    map[string]any{"type": "string"},
				"labels":   map[string]any{"type": "array", "items": map[string]any{"type": "string"}},
				"metadata": map[string]any{"type": "object"},
			},
		},
		Endpoint: spec.Endpoint{BaseURL: "https://api.example.com/v1"},
		Binding: spec.Binding{
			Method: "POST",
			Path:   "/repos/{owner}/{repo}/issues",
			StaticHeaders: map[string]string{
				"Accept": "application/vnd.example+json",
			},
			Parameters: []spec.Parameter{
				{From: "/owner", In: "path", Name: "owner", Style: "simple"},
				{From: "/repo", In: "path", Name: "repo", Style: "simple"},
				{From: "/labels", In: "query", Name: "labels", Style: "form", Explode: true},
			},
			Body: &spec.Body{
				ContentType: "application/json",
				Mappings: []spec.BodyMapping{
					{From: "/title", To: "/title"},
					{From: "/metadata", To: "/metadata"},
				},
			},
			Response: spec.Response{
				Mode: "passthrough", SuccessStatuses: []int{201},
				ContentTypes: []string{"application/json"},
			},
		},
	}
}
