package tianyancha

import (
	"context"
	"crypto/tls"
	"net/http"
	"net/http/httptest"
	"net/url"
	"testing"

	"github.com/stretchr/testify/require"

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

// probeCredentials mirrors the external wire fixtures.
var probeCredentials = runner.Credentials{"apiKey": "tyc-token"}

// newProbeServer answers every request with the scripted status and body and
// records the raw query and headers the probe actually sent.
func newProbeServer(t *testing.T, status int, body string) (*httptest.Server, *[]url.Values) {
	t.Helper()
	queries := &[]url.Values{}
	server := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		query, err := url.ParseQuery(r.URL.RawQuery)
		require.NoError(t, err)
		*queries = append(*queries, query)
		w.Header().Set("Content-Type", "application/json")
		w.WriteHeader(status)
		_, _ = w.Write([]byte(body))
	}))
	t.Cleanup(server.Close)
	return server, queries
}

func newProbeClient(t *testing.T, server *httptest.Server) *runner.RESTClient {
	t.Helper()
	client, err := runner.NewRESTClient(runner.RESTOptions{
		BaseURL:          server.URL,
		Auth:             AuthSpec(),
		StatusClassifier: ClassifyStatus,
		Transport:        &http.Transport{TLSClientConfig: &tls.Config{InsecureSkipVerify: true}},
	})
	require.NoError(t, err)
	return client
}

// TestValidateCredentialsProbeClassifiesEnvelope locks the probe onto the
// same closed error_code table the actions use: every 200-envelope business
// failure keeps its classified meaning, so an entitlement rejection (valid
// token, missing API package) no longer masquerades as a bad credential.
func TestValidateCredentialsProbeClassifiesEnvelope(t *testing.T) {
	t.Parallel()

	cases := []struct {
		name string
		body string
		want runner.ErrorCode
	}{
		{name: "zero-code", body: `{"error_code":0,"reason":"","result":{}}`},
		{name: "quota-code", body: `{"error_code":300004,"reason":"超出当日调用量"}`, want: runner.ErrorRateLimited},
		{name: "frequency-code", body: `{"error_code":300006,"reason":"请求频率过高"}`, want: runner.ErrorRateLimited},
		{name: "daily-quota-code", body: `{"error_code":300007,"reason":"超出当日调用量"}`, want: runner.ErrorRateLimited},
		{name: "token-code", body: `{"error_code":300002,"reason":"token错误"}`, want: runner.ErrorProviderAuthUnavailable},
		{name: "token-expired", body: `{"error_code":300003,"reason":"token过期"}`, want: runner.ErrorProviderAuthUnavailable},
		{name: "token-invalid", body: `{"error_code":300009,"reason":"token无效"}`, want: runner.ErrorProviderAuthUnavailable},
		{name: "entitlement-code", body: `{"error_code":300005,"reason":"无权限"}`, want: runner.ErrorPermissionDenied},
		{name: "entitlement-package-code", body: `{"error_code":300011,"reason":"无此API套餐"}`, want: runner.ErrorPermissionDenied},
		{name: "param-code", body: `{"error_code":300008,"reason":"参数错误"}`, want: runner.ErrorValidation},
		{name: "unknown-code", body: `{"error_code":310000,"reason":"其他错误"}`, want: runner.ErrorUpstreamFailure},
		{name: "null-sentinel", body: `{"error_code":300000,"reason":"无数据"}`, want: runner.ErrorUpstreamFailure},
	}
	for _, testCase := range cases {
		t.Run(testCase.name, func(t *testing.T) {
			t.Parallel()

			server, queries := newProbeServer(t, http.StatusOK, testCase.body)
			client := newProbeClient(t, server)

			err := validateCredentials(context.Background(), client, probeCredentials)
			if testCase.want == "" {
				require.NoError(t, err)
			} else {
				require.Equal(t, testCase.want, runner.CodeOf(err))
			}

			// The probe is the fixed entry-level search with the token in the
			// Authorization header, exactly like every action call.
			require.Len(t, *queries, 1)
			require.Equal(t, []string{"百度"}, (*queries)[0]["word"])
			require.Len(t, (*queries)[0], 1)
		})
	}
}

// TestValidateCredentialsProbeInvalidEnvelope fails closed on bodies without
// a decidable error_code.
func TestValidateCredentialsProbeInvalidEnvelope(t *testing.T) {
	t.Parallel()

	for name, body := range map[string]string{
		"non-json":   `<html>gateway error</html>`,
		"no-code":    `{"result":{}}`,
		"null-code":  `{"error_code":null}`,
		"float-code": `{"error_code":300005.5}`,
	} {
		server, _ := newProbeServer(t, http.StatusOK, body)
		client := newProbeClient(t, server)
		err := validateCredentials(context.Background(), client, probeCredentials)
		require.Equalf(t, runner.ErrorUpstreamFailure, runner.CodeOf(err),
			"body %q must fail closed as upstream failure", name)
	}
}
