package jin10

import (
	"encoding/json"
	"strings"
	"testing"

	"github.com/stretchr/testify/require"

	"github.com/tidbcloud/AgentTeam/internal/extension/runner"
	"github.com/tidbcloud/AgentTeam/internal/extension/runner/mcp"
)

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

	validPayload := `{"data":null,"status":200,"message":"ok"}`
	testCases := []struct {
		name   string
		result mcp.ToolResult
		want   string
	}{
		{
			name: "structured content",
			result: mcp.ToolResult{
				StructuredContent: json.RawMessage(validPayload),
			},
			want: validPayload,
		},
		{
			name: "single JSON text content",
			result: mcp.ToolResult{
				Content: []json.RawMessage{
					json.RawMessage(
						`{"type":"text","text":"{\"data\":null,\"status\":200,\"message\":\"ok\"}"}`,
					),
				},
			},
			want: validPayload,
		},
		{name: "empty result"},
		{
			name: "wire structured null does not use text fallback",
			result: mcp.ToolResult{
				StructuredContentPresent: true,
				Content: []json.RawMessage{
					json.RawMessage(
						`{"type":"text","text":"{\"data\":null,\"status\":200,\"message\":\"ok\"}"}`,
					),
				},
			},
		},
		{
			name: "structured whitespace does not use text fallback",
			result: mcp.ToolResult{
				StructuredContent: json.RawMessage(" \n\t "),
				Content: []json.RawMessage{
					json.RawMessage(
						`{"type":"text","text":"{\"data\":null,\"status\":200,\"message\":\"ok\"}"}`,
					),
				},
			},
		},
		{
			name: "multiple text contents",
			result: mcp.ToolResult{
				Content: []json.RawMessage{
					json.RawMessage(`{"type":"text","text":"{}"}`),
					json.RawMessage(`{"type":"text","text":"{}"}`),
				},
			},
		},
		{
			name: "non-text content",
			result: mcp.ToolResult{
				Content: []json.RawMessage{
					json.RawMessage(`{"type":"image","data":"abc"}`),
				},
			},
		},
		{
			name: "empty text",
			result: mcp.ToolResult{
				Content: []json.RawMessage{
					json.RawMessage(`{"type":"text","text":""}`),
				},
			},
		},
	}
	for _, testCase := range testCases {
		testCase := testCase
		t.Run(testCase.name, func(t *testing.T) {
			t.Parallel()
			raw, err := normalizeToolResult(testCase.result)
			if testCase.want == "" {
				require.Nil(t, raw)
				require.Equal(
					t,
					runner.ErrorUpstreamFailure,
					runner.CodeOf(err),
				)
				return
			}
			require.NoError(t, err)
			require.JSONEq(t, testCase.want, string(raw))
		})
	}
}

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

	valid := mcp.ResourceContent{
		URI:  QuoteCodesResourceURI,
		Text: `{"data":[{"code":"XAUUSD","name":"Gold"}],"status":200,"message":"ok"}`,
	}
	testCases := []struct {
		name     string
		contents []mcp.ResourceContent
		wantOK   bool
	}{
		{name: "single fixed text resource", contents: []mcp.ResourceContent{valid}, wantOK: true},
		{name: "empty content"},
		{name: "multiple contents", contents: []mcp.ResourceContent{valid, valid}},
		{
			name: "wrong URI",
			contents: []mcp.ResourceContent{{
				URI: "quote://other", Text: valid.Text,
			}},
		},
		{
			name: "blob content",
			contents: []mcp.ResourceContent{{
				URI: QuoteCodesResourceURI, Blob: []byte("{}"),
			}},
		},
		{
			name: "mixed text and blob",
			contents: []mcp.ResourceContent{{
				URI: QuoteCodesResourceURI, Text: valid.Text, Blob: []byte("x"),
			}},
		},
	}
	for _, testCase := range testCases {
		testCase := testCase
		t.Run(testCase.name, func(t *testing.T) {
			t.Parallel()
			raw, err := normalizeResourceResult(mcp.ResourceResult{
				Contents: testCase.contents,
			})
			if !testCase.wantOK {
				require.Nil(t, raw)
				require.Equal(
					t,
					runner.ErrorUpstreamFailure,
					runner.CodeOf(err),
				)
				return
			}
			require.NoError(t, err)
			require.JSONEq(t, valid.Text, string(raw))
		})
	}
}

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

	validators, err := compileCatalogValidators(Definition())
	require.NoError(t, err)

	for _, invalid := range []string{
		`{"code":"XAUUSD","count":101}`,
		`{"code":"XAUUSD","count":null}`,
		`{"code":"XAUUSD","unknown":true}`,
		`{"code":""}`,
		`{"code":"XAUUSD","code":"EURUSD"}`,
	} {
		_, err := validators.validateInput(
			ActionGetKline,
			json.RawMessage(invalid),
		)
		require.Equal(t, runner.ErrorValidation, runner.CodeOf(err))
	}

	for _, invalid := range []string{
		`{"data":null,"status":200}`,
		`{"data":null,"status":"200","message":"ok"}`,
		`{"data":{"code":"XAUUSD"},"status":200,"message":"ok"}`,
		`{"data":null,"status":200,"message":"ok","sdkEnvelope":{}}`,
		`{"data":null,"status":401,"message":"unauthorized"}`,
		`{"data":null,"status":500,"message":"upstream failed"}`,
		`{"data":null,"status":500,"status":200,"message":"ok"}`,
		`{"data":{"code":"XAUUSD","code":"EURUSD"},"status":200,"message":"ok"}`,
	} {
		_, err := validators.validateOutput(
			ActionGetQuote,
			json.RawMessage(invalid),
		)
		require.Equal(t, runner.ErrorUpstreamFailure, runner.CodeOf(err))
	}

	oversize := json.RawMessage(
		`{"data":null,"status":200,"message":"` +
			strings.Repeat("x", runner.MaxResponseBytes) +
			`"}`,
	)
	_, err = validators.validateOutput(ActionGetQuote, oversize)
	require.Equal(t, runner.ErrorUpstreamFailure, runner.CodeOf(err))

	invalidUTF8 := json.RawMessage{
		'{', '"', 'd', 'a', 't', 'a', '"', ':', 'n', 'u', 'l', 'l', ',',
		'"', 's', 't', 'a', 't', 'u', 's', '"', ':', '2', '0', '0', ',',
		'"', 'm', 'e', 's', 's', 'a', 'g', 'e', '"', ':', '"', 0xff, '"', '}',
	}
	_, err = validators.validateOutput(ActionGetQuote, invalidUTF8)
	require.Equal(t, runner.ErrorUpstreamFailure, runner.CodeOf(err))

	for _, success := range []string{
		`{"data":null,"status":200,"message":"ok"}`,
		`{"data":null,"status":200.0,"message":"ok"}`,
		`{"data":null,"status":2e2,"message":"ok"}`,
	} {
		_, err = validators.validateOutput(
			ActionGetQuote,
			json.RawMessage(success),
		)
		require.NoError(t, err)
	}
}

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

	const raw = `{"code":"XAUUSD","time":2288457142539548672053,"count":100}`
	validators, err := compileCatalogValidators(Definition())
	require.NoError(t, err)
	input, err := validators.validateInput(
		ActionGetKline,
		json.RawMessage(raw),
	)
	require.NoError(t, err)
	arguments, err := decodeToolArguments(inputKline, input)
	require.NoError(t, err)
	encoded, err := json.Marshal(arguments)
	require.NoError(t, err)
	require.JSONEq(t, raw, string(encoded))
}
