package feishubot

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

	"github.com/stretchr/testify/require"

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

// newProbeServer is a TLS loopback double bound to the package-local webhook
// call, used to classify the credential validation probe.
func newProbeServer(t *testing.T, status int, body string) (*httptest.Server, *runner.RESTClient) {
	t.Helper()
	server := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		_, err := io.ReadAll(r.Body)
		require.NoError(t, err)
		w.Header().Set("Content-Type", "application/json")
		w.WriteHeader(status)
		_, _ = w.Write([]byte(body))
	}))
	t.Cleanup(server.Close)
	client, err := runner.NewRESTClient(runner.RESTOptions{
		BaseURL: server.URL,
		Auth:    AuthSpec(),
		Transport: &http.Transport{
			TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
		},
	})
	require.NoError(t, err)
	return server, client
}

func probeCredentials() runner.Credentials {
	return runner.Credentials{
		APIKeyField:        "token123",
		SigningSecretField: "secret123",
	}
}

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

	cases := []struct {
		name    string
		status  int
		body    string
		wantErr bool
		code    runner.ErrorCode
	}{
		{
			name:   "invalid msg type proves the webhook",
			status: http.StatusOK, body: `{"code":9499,"msg":"Bad request"}`,
		},
		{
			name:   "keyword miss proves the webhook",
			status: http.StatusOK, body: `{"code":19024,"msg":"keywords not in content"}`,
		},
		{
			name:   "legacy message proves the webhook",
			status: http.StatusOK, body: `{"code":123,"msg":"params error, unknown content value"}`,
		},
		{
			name:   "sign mismatch rejects the credential",
			status: http.StatusOK, body: `{"code":19021,"msg":"sign match fail"}`,
			wantErr: true, code: runner.ErrorProviderAuthUnavailable,
		},
		{
			name:   "rate limited probe surfaces",
			status: http.StatusOK, body: `{"code":11232,"msg":"too many"}`,
			wantErr: true, code: runner.ErrorRateLimited,
		},
		{
			name:   "malformed probe body fails closed",
			status: http.StatusOK, body: `oops`,
			wantErr: true, code: runner.ErrorUpstreamFailure,
		},
		{
			name:   "empty probe body fails closed without panicking",
			status: http.StatusOK, body: ``,
			wantErr: true, code: runner.ErrorUpstreamFailure,
		},
		{
			name:   "null probe body fails closed without panicking",
			status: http.StatusOK, body: `null`,
			wantErr: true, code: runner.ErrorUpstreamFailure,
		},
		{
			name:   "http auth failure rejects the credential",
			status: http.StatusForbidden, body: `{}`,
			wantErr: true, code: runner.ErrorProviderAuthUnavailable,
		},
	}
	for _, testCase := range cases {
		t.Run(testCase.name, func(t *testing.T) {
			t.Parallel()
			_, client := newProbeServer(t, testCase.status, testCase.body)
			_, _, err := callWebhook(context.Background(), client, probeCredentials(),
				map[string]any{"msg_type": "__validation_probe__"})
			if !testCase.wantErr {
				require.NoError(t, err)
				return
			}
			require.Error(t, err)
			require.Equal(t, testCase.code, runner.CodeOf(err))
		})
	}
}

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

	require.Equal(t, "abc123", mustResolveToken(t, "abc123"))
	require.Equal(t, "abc123", mustResolveToken(t, " https://open.feishu.cn/open-apis/bot/v2/hook/abc123 "))

	for _, invalid := range []string{
		"",
		"abc/123",
		"http://open.feishu.cn/open-apis/bot/v2/hook/abc123",
		"https://evil.example.com/open-apis/bot/v2/hook/abc123",
		"https://open.feishu.cn/api/bot/abc123",
		"https://open.feishu.cn/open-apis/bot/v2/hook/",
		"https://open.feishu.cn/open-apis/bot/v2/hook/a/b",
	} {
		_, err := resolveWebhookToken(invalid)
		require.Errorf(t, err, "apiKey %q must be rejected", invalid)
		require.Equal(t, runner.ErrorValidation, runner.CodeOf(err))
	}
}

func mustResolveToken(t *testing.T, apiKey string) string {
	t.Helper()
	token, err := resolveWebhookToken(apiKey)
	require.NoError(t, err)
	return token
}
