package feishubot_test

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

	"github.com/stretchr/testify/require"

	"github.com/tidbcloud/AgentTeam/internal/extension"
	"github.com/tidbcloud/AgentTeam/internal/extension/bootstrap"
	"github.com/tidbcloud/AgentTeam/internal/extension/providers/feishubot"
	"github.com/tidbcloud/AgentTeam/internal/extension/registry"
	"github.com/tidbcloud/AgentTeam/internal/extension/runner"
	"github.com/tidbcloud/AgentTeam/internal/service/extensionflow"
	appregistry "github.com/tidbcloud/AgentTeam/internal/store/app/registry"
	"github.com/tidbcloud/AgentTeam/internal/store/model"
)

func insecureTransport() *http.Transport {
	return &http.Transport{TLSClientConfig: &tls.Config{InsecureSkipVerify: true}}
}

type staticCredentialSource struct {
	credentials runner.Credentials
}

func (source staticCredentialSource) ResolveProviderCredentials(
	context.Context, string, string,
) (runner.Credentials, error) {
	return source.credentials, nil
}

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

func invocation(action string, input string) registry.BuiltinInvocation {
	return registry.BuiltinInvocation{
		WorkspaceID:           "ws",
		ServiceInstallationID: "inst",
		ExtensionName:         feishubot.Domain,
		Action:                action,
		Input:                 json.RawMessage(input),
	}
}

// newFeishuServer is a TLS loopback double that replies with the given status
// and body and captures the latest request.
func newFeishuServer(t *testing.T, status int, body string) (*httptest.Server, *capturedRequest) {
	t.Helper()
	captured := &capturedRequest{}
	server := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		bodyBytes, err := io.ReadAll(r.Body)
		require.NoError(t, err)
		captured.method = r.Method
		captured.path = r.URL.Path
		captured.query = r.URL.RawQuery
		captured.body = string(bodyBytes)
		w.Header().Set("Content-Type", "application/json")
		w.WriteHeader(status)
		_, _ = w.Write([]byte(body))
	}))
	t.Cleanup(server.Close)
	return server, captured
}

type capturedRequest struct {
	method string
	path   string
	query  string
	body   string
}

func agentteamClient(t *testing.T, server *httptest.Server) *runner.RESTClient {
	t.Helper()
	client, err := runner.NewRESTClient(runner.RESTOptions{
		BaseURL:   server.URL,
		Auth:      feishubot.AuthSpec(),
		Transport: insecureTransport(),
	})
	require.NoError(t, err)
	return client
}

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

	extensionUnit, err := feishubot.Extension(staticCredentialSource{})
	require.NoError(t, err)

	names := make([]string, 0)
	for _, action := range extensionUnit.Definition.Actions {
		names = append(names, action.Name)
	}
	lister, ok := extensionUnit.Implementation.Executor.(runner.ActionLister)
	require.True(t, ok)
	require.ElementsMatch(t, names, lister.ActionNames())

	require.Equal(t, "feishucustombot-rest-v1", extensionUnit.Implementation.Revision)
	require.Equal(t, appregistry.FeishuCustomBotAppID, extensionUnit.Distribution.AppID)
	require.Equal(t, appregistry.FeishuCustomBotAppSlug, extensionUnit.Distribution.AppSlug)
	require.True(t, appregistry.IsProductApp(
		extensionUnit.Distribution.AppID, extensionUnit.Distribution.AppSlug))
	require.NoError(t, bootstrap.Register(registry.New(), []extension.Extension{extensionUnit}))
}

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

	extensionUnit, err := feishubot.Extension(staticCredentialSource{})
	require.NoError(t, err)
	reg := registry.New()
	require.NoError(t, bootstrap.Register(reg, []extension.Extension{extensionUnit}))
	entry, ok := reg.LookupByIdentity(feishubot.Domain, 1)
	require.True(t, ok)
	require.Equal(t, model.FeishuCustomBotBuiltinExtensionDefinitionDigest, entry.Definition.Digest)
}

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

	spec := feishubot.AuthSpec()
	require.Equal(t, runner.AuthCredential, spec.Kind)
	require.NoError(t, spec.Validate(validCredentials()))
	// The optional signing secret may be omitted.
	require.NoError(t, spec.Validate(runner.Credentials{
		feishubot.APIKeyField: "token123",
	}))
	require.Error(t, spec.Validate(runner.Credentials{}),
		"the webhook token is required")
}

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

	cases := []struct {
		name   string
		status int
		body   string
		code   extensionflow.InvocationErrorCode
	}{
		{
			name:   "business rate limit",
			status: http.StatusOK, body: `{"code":11232,"msg":"too many"}`,
			code: extensionflow.InvocationRateLimited,
		},
		{
			name:   "signature rejection blames the connection",
			status: http.StatusOK, body: `{"code":19021,"msg":"sign match fail"}`,
			code: extensionflow.InvocationProviderAuthUnavailable,
		},
		{
			name:   "ip whitelist rejection blames the connection",
			status: http.StatusOK, body: `{"code":19022,"msg":"Ip is not in whitelist"}`,
			code: extensionflow.InvocationProviderAuthUnavailable,
		},
		{
			name:   "unknown business code fails closed",
			status: http.StatusOK, body: `{"code":19999,"msg":"mystery"}`,
			code: extensionflow.InvocationUpstreamFailure,
		},
		{
			name:   "numeric string code still classifies",
			status: http.StatusOK, body: `{"code":"19024","msg":"keyword"}`,
			code: extensionflow.InvocationValidation,
		},
		{
			name:   "object envelope without code fails closed",
			status: http.StatusOK, body: `{"msg":"bad"}`,
			code: extensionflow.InvocationUpstreamFailure,
		},
		{
			name:   "malformed body fails closed",
			status: http.StatusOK, body: `plain text`,
			code: extensionflow.InvocationUpstreamFailure,
		},
		{
			name:   "empty body fails closed",
			status: http.StatusOK, body: ``,
			code: extensionflow.InvocationUpstreamFailure,
		},
		{
			name:   "http rate limit",
			status: http.StatusTooManyRequests, body: `{}`,
			code: extensionflow.InvocationRateLimited,
		},
		{
			name:   "http auth failure",
			status: http.StatusUnauthorized, body: `{}`,
			code: extensionflow.InvocationProviderAuthUnavailable,
		},
	}
	for _, testCase := range cases {
		t.Run(testCase.name, func(t *testing.T) {
			t.Parallel()
			server, _ := newFeishuServer(t, testCase.status, testCase.body)
			executor := runner.NewRESTExecutor(feishubot.Domain,
				agentteamClient(t, server), staticCredentialSource{validCredentials()},
				feishubot.Handlers())
			_, err := executor.Invoke(context.Background(),
				invocation(feishubot.ActionSendTextMessage, `{"text":"hi"}`))
			require.Error(t, err)
			code, classified := extensionflow.InvocationErrorCodeOf(err)
			require.True(t, classified)
			require.Equal(t, testCase.code, code)
		})
	}
}

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

	server, _ := newFeishuServer(t, http.StatusOK,
		`{"code":0,"msg":"success","StatusCode":0,"StatusMessage":"success"}`)
	executor := runner.NewRESTExecutor(feishubot.Domain,
		agentteamClient(t, server), staticCredentialSource{validCredentials()},
		feishubot.Handlers())
	result, err := executor.Invoke(context.Background(),
		invocation(feishubot.ActionSendTextMessage, `{"text":"hi"}`))
	require.NoError(t, err)
	var payload map[string]any
	require.NoError(t, json.Unmarshal(result.Data, &payload))
	require.Equal(t, float64(0), payload["code"])
	require.Equal(t, "success", payload["msg"])
	require.Equal(t, map[string]any{}, payload["data"])
	require.Equal(t, float64(0), payload["statusCode"])
	require.Equal(t, "success", payload["statusMessage"])
}
