package worker

import (
	"context"
	"encoding/json"
	"errors"
	"net/http"
	"net/http/httptest"
	"strings"
	"testing"
	"time"

	common "github.com/pingcap-inc/nexuscrm-contract/pkg/runtime/commonv1"
	query "github.com/pingcap-inc/nexuscrm-contract/pkg/runtime/queryv1"
	mutation "github.com/pingcap-inc/nexuscrm-contract/pkg/runtime/recordmutationv1"
)

func testContext(t *testing.T) context.Context {
	t.Helper()
	ctx, cancel := context.WithTimeout(context.Background(), time.Second)
	t.Cleanup(cancel)
	return ctx
}

func TestQueryNeverDropsPredicatesOrAcceptsAuthorityArguments(t *testing.T) {
	ctx := testContext(t)
	request, err := lowerQuery(ctx, json.RawMessage(`{"object":"Account","fields":["Id","Name"],"filters":[{"field":"Revenue__c","operator":"EQUAL","value":9007199254740993.01}]}`), true)
	if err != nil {
		t.Fatal(err)
	}
	if request.Filter.Kind != query.PredicateAnd || len(request.Filter.Children) != 2 {
		t.Fatalf("lost filter: %#v", request.Filter)
	}
	decimal := request.Filter.Children[0].Value.Decimal
	if decimal == nil || decimal.Coefficient != "900719925474099301" || decimal.Scale != 2 {
		t.Fatalf("decimal rounded: %#v", decimal)
	}
	owner := request.Filter.Children[1]
	if owner.Right.Kind != query.OperandTrustedContext || owner.Right.TrustedContext != query.TrustedContextPrincipalID {
		t.Fatal("owner identity is not Runtime-owned")
	}
	for _, raw := range []string{
		`{"object":"Account","fields":["Id"],"filter":"Name = 'x'"}`,
		`{"object":"Account","fields":["Id"],"user_id":"admin"}`,
		`{"object":"Account","fields":["Id"],"filters":[{"field":"Name","operator":"IN","value":"x"}]}`,
		`{"object":"Account","fields":["Id"],"filters":[{"field":"Name","operator":"EQUAL"}]}`,
		`{"object":"Account","fields":["Id"],"filters":[{"field":"Name","operator":"EQUAL","value":{},"ignored":true}]}`,
		`{"object":"Account","fields":["Id"],"limit":201}`,
	} {
		if _, err := lowerQuery(ctx, json.RawMessage(raw), false); err == nil {
			t.Fatalf("accepted %s", raw)
		}
	}
}

func TestMutationPreservesExactValuesAndStableCommandIdentity(t *testing.T) {
	ctx := testContext(t)
	first := `{"idempotency_key":"create-1","operation":"CREATE","object":"Account","fields":{"Name":"Example","Revenue__c":9007199254740993.01,"Active__c":false,"Nullable__c":null}}`
	request, err := lowerMutation(ctx, json.RawMessage(first))
	if err != nil {
		t.Fatal(err)
	}
	op := request.Operations[0]
	if request.UseCase != mutation.MutationUseCaseSingle || request.BatchMode != mutation.BatchIndependent || len(op.Fields) != 4 {
		t.Fatalf("bad mutation: %#v", request)
	}
	if !op.Fields[2].Null || *op.Fields[3].Number != "9007199254740993.01" || *op.Fields[0].Boolean {
		t.Fatal("source values changed")
	}
	second, err := lowerMutation(ctx, json.RawMessage(`{"fields":{"Revenue__c":9007199254740993.01,"Nullable__c":null,"Active__c":false,"Name":"Example"},"object":"Account","operation":"CREATE","idempotency_key":"create-1"}`))
	if err != nil {
		t.Fatal(err)
	}
	a, err := mutation.CanonicalCommandBytes(request)
	if err != nil {
		t.Fatal(err)
	}
	b, err := mutation.CanonicalCommandBytes(second)
	if err != nil {
		t.Fatal(err)
	}
	if string(a) != string(b) || request.CommandID != second.CommandID {
		t.Fatal("retry command changed with JSON property order")
	}
	changed, err := lowerMutation(ctx, json.RawMessage(strings.Replace(first, "Example", "Changed", 1)))
	if err != nil {
		t.Fatal(err)
	}
	c, err := mutation.CanonicalCommandBytes(changed)
	if err != nil {
		t.Fatal(err)
	}
	if changed.CommandID != request.CommandID || string(c) == string(a) {
		t.Fatal("same key must preserve identity and expose changed payload to Runtime conflict checking")
	}
	for _, raw := range []string{
		`{"idempotency_key":"x","operation":"UPSERT","object":"Account","fields":{"Name":"Example"},"external_identity":{"field":"External_Id__c","value":"one"}}`,
		`{"idempotency_key":"x","operation":"UPSERT","object":"Account"}`,
		`{"idempotency_key":"x","operation":"DELETE","object":"Account"}`,
		`{"idempotency_key":"x","operation":"CREATE","object":"Account","record_id":"other"}`,
		`{"idempotency_key":"x","operation":"CREATE","object":"Account","principal":"admin"}`,
	} {
		if _, err := lowerMutation(ctx, json.RawMessage(raw)); err == nil {
			t.Fatalf("accepted %s", raw)
		}
	}
}

type admittedCapabilityKey struct{}

func TestRuntimeIdentityAndPermissionsOwnEveryToolCall(t *testing.T) {
	allowWrite, revoked := false, false
	authCalls, queryCalls, writeCalls := 0, 0, 0
	ports := Ports{
		Authorize: func(ctx context.Context, bearer string, caps []string) (context.Context, error) {
			authCalls++
			if bearer != "native-user-session" {
				t.Fatal("user identity substituted")
			}
			if len(caps) != 1 || (caps[0] != query.CapabilityName && caps[0] != mutation.CapabilityName) {
				t.Fatal("unexpected capability request")
			}
			if revoked {
				return nil, &common.Failure{Category: common.FailureUnauthenticated}
			}
			return context.WithValue(ctx, admittedCapabilityKey{}, caps[0]), nil
		},
		Query: func(ctx context.Context, bearer string, request query.Request) (query.Response, error) {
			queryCalls++
			if ctx.Value(admittedCapabilityKey{}) != query.CapabilityName {
				t.Fatal("query admitted with wrong capability")
			}
			if bearer != "native-user-session" {
				t.Fatal("query did not carry user bearer")
			}
			if _, ok := ctx.Deadline(); !ok {
				t.Fatal("no request deadline")
			}
			name := "Visible"
			return query.Response{Done: true, Records: []query.Record{{Fields: []query.Field{{Name: "Name", Value: query.Value{Kind: query.ValueString, String: &name}}}}}}, nil
		},
		Mutate: func(ctx context.Context, bearer string, _ mutation.Request) (mutation.Response, error) {
			writeCalls++
			if ctx.Value(admittedCapabilityKey{}) != mutation.CapabilityName {
				t.Fatal("write inherited read delegation")
			}
			if bearer != "native-user-session" {
				t.Fatal("write did not carry user bearer")
			}
			if !allowWrite {
				return mutation.Response{}, &common.Failure{Category: common.FailureForbidden}
			}
			return mutation.Response{Results: []mutation.OperationResult{{Success: true, Kind: mutation.OperationCreate, RecordID: "created"}}}, nil
		},
		Status: func(ctx context.Context, _ string, _ mutation.Request) (mutation.StatusResponse, error) {
			if ctx.Value(admittedCapabilityKey{}) != mutation.CapabilityName {
				t.Fatal("status inherited read delegation")
			}
			return mutation.StatusResponse{}, errors.New("private diagnostic must never escape")
		},
	}
	w, err := New(Config{PublicOrigin: "https://crm.example", Version: "test", RequestTimeout: time.Second, Ports: ports})
	if err != nil {
		t.Fatal(err)
	}
	call := func(name, args string) *httptest.ResponseRecorder {
		r := httptest.NewRequest(http.MethodPost, "https://crm.example/mcp", strings.NewReader(`{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"`+name+`","arguments":`+args+`}}`))
		r.Header.Set("Authorization", "Bearer native-user-session")
		r.Header.Set("Content-Type", "application/json")
		r.Header.Set("Accept", "application/json, text/event-stream")
		out := httptest.NewRecorder()
		w.ServeHTTP(out, r)
		return out
	}
	out := call("crm_query_records", `{"object":"Account","fields":["Name"]}`)
	if out.Code != 200 || !strings.Contains(out.Body.String(), "Visible") {
		t.Fatalf("query: %d %s", out.Code, out.Body.String())
	}
	args := `{"operation":"CREATE","object":"Account","idempotency_key":"one","fields":{"Name":"Example"}}`
	out = call("crm_mutate_record", args)
	if !strings.Contains(out.Body.String(), "FORBIDDEN") {
		t.Fatal("Runtime denial lost")
	}
	allowWrite = true
	out = call("crm_mutate_record", args)
	if !strings.Contains(out.Body.String(), "created") {
		t.Fatal("Worker imposed its own read-only restriction")
	}
	out = call("crm_mutation_status", args)
	if strings.Contains(out.Body.String(), "private diagnostic") || !strings.Contains(out.Body.String(), "UNAVAILABLE") {
		t.Fatal("unsafe failure projection")
	}
	revoked = true
	out = call("crm_query_records", `{"object":"Account","fields":["Name"]}`)
	if out.Code != 401 || queryCalls != 1 || writeCalls != 2 || authCalls != 5 {
		t.Fatalf("revoked call reached data plane: %d %d %d %d", out.Code, queryCalls, writeCalls, authCalls)
	}
	w.BeginDrain()
	if out = call("crm_query_records", `{}`); out.Code != 503 || authCalls != 5 {
		t.Fatal("draining Worker admitted request")
	}
}

func TestProjectionPreservesOmissionNullAndDecimals(t *testing.T) {
	value, err := projectRecord(query.Record{Fields: []query.Field{
		{Name: "Amount", Value: query.Value{Kind: query.ValueDecimal, Decimal: &query.Decimal{Coefficient: "900719925474099301", Scale: 2}}},
		{Name: "Empty", Value: query.Value{Null: true}},
	}})
	if err != nil {
		t.Fatal(err)
	}
	raw, err := json.Marshal(value)
	if err != nil {
		t.Fatal(err)
	}
	if string(raw) != `{"Amount":900719925474099301e-2,"Empty":null}` {
		t.Fatalf("changed values: %s", raw)
	}
	if _, found := value["Hidden"]; found {
		t.Fatal("invented FLS-omitted field")
	}
}

func TestProtocolAndWriteCapabilityAdmissionAreSeparate(t *testing.T) {
	var requested []string
	writes := 0
	ports := Ports{
		Authorize: func(ctx context.Context, _ string, caps []string) (context.Context, error) {
			requested = append(requested, caps...)
			if caps[0] == mutation.CapabilityName {
				return nil, &common.Failure{Category: common.FailureForbidden}
			}
			return ctx, nil
		},
		Query: func(context.Context, string, query.Request) (query.Response, error) { return query.Response{}, nil },
		Mutate: func(context.Context, string, mutation.Request) (mutation.Response, error) {
			writes++
			return mutation.Response{}, nil
		},
		Status: func(context.Context, string, mutation.Request) (mutation.StatusResponse, error) {
			writes++
			return mutation.StatusResponse{}, nil
		},
	}
	w, err := New(Config{PublicOrigin: "https://crm.example", Version: "test", RequestTimeout: time.Second, Ports: ports})
	if err != nil {
		t.Fatal(err)
	}
	for _, test := range []struct {
		body string
		code int
	}{
		{`{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-11-25"}}`, 200},
		{`{"jsonrpc":"2.0","id":2,"method":"tools/list"}`, 200},
		{`{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"crm_mutate_record","arguments":{}}}`, 403},
		{`{"jsonrpc":"2.0","id":4,"method":"tools/call","params":{"name":"crm_mutation_status","arguments":{}}}`, 403},
	} {
		r := httptest.NewRequest("POST", "https://crm.example/mcp", strings.NewReader(test.body))
		r.Header.Set("Authorization", "Bearer native-read-user")
		r.Header.Set("Accept", "application/json, text/event-stream")
		r.Header.Set("Content-Type", "application/json")
		out := httptest.NewRecorder()
		w.ServeHTTP(out, r)
		if out.Code != test.code {
			t.Fatalf("got %d want %d", out.Code, test.code)
		}
	}
	want := []string{query.CapabilityName, query.CapabilityName, mutation.CapabilityName, mutation.CapabilityName}
	if strings.Join(requested, ",") != strings.Join(want, ",") || writes != 0 {
		t.Fatal("wrong capability or denied write reached Runtime mutation")
	}
}
