package feishubot_test

import (
	"context"
	"crypto/hmac"
	"crypto/sha256"
	"encoding/base64"
	"encoding/json"
	"net/http"
	"testing"

	"github.com/stretchr/testify/require"

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

// wireCase pins one action's upstream wire contract: method, path, and body
// semantics, exercised against a TLS loopback double.
type wireCase struct {
	action string
	input  string
	// bodyContains asserts substrings of the JSON request body.
	bodyContains []string
	// bodyAbsent asserts keys absent from the request body.
	bodyAbsent []string
}

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

	cases := []wireCase{
		{
			action: feishubot.ActionSendTextMessage,
			input:  `{"text":"Deploy finished"}`,
			bodyContains: []string{
				`"msg_type":"text"`, `"content":{"text":"Deploy finished"}`,
			},
			bodyAbsent: []string{"timestamp", "sign"},
		},
		{
			action: feishubot.ActionSendPostMessage,
			input:  `{"post":{"zh_cn":{"title":"T","content":[[{"tag":"text","text":"hi"}]]}}}`,
			bodyContains: []string{
				`"msg_type":"post"`, `"post":{"zh_cn"`,
			},
		},
		{
			action:       feishubot.ActionSendImageMessage,
			input:        `{"imageKey":"img_v2_1234"}`,
			bodyContains: []string{`"msg_type":"image"`, `"image_key":"img_v2_1234"`},
		},
		{
			action:       feishubot.ActionSendShareChatMessage,
			input:        `{"shareChatId":"oc_1234"}`,
			bodyContains: []string{`"msg_type":"share_chat"`, `"share_chat_id":"oc_1234"`},
		},
		{
			action:       feishubot.ActionSendInteractiveCard,
			input:        `{"card":{"schema":"2.0"}}`,
			bodyContains: []string{`"msg_type":"interactive"`, `"card":{"schema":"2.0"}`},
			bodyAbsent:   []string{"content"},
		},
	}
	definition := feishubot.Definition()
	covered := make(map[string]struct{}, len(cases))
	for _, testCase := range cases {
		covered[testCase.action] = struct{}{}
	}
	for _, action := range definition.Actions {
		require.Containsf(t, covered, action.Name,
			"every catalog action needs a wire case: %s", action.Name)
	}

	for _, testCase := range cases {
		t.Run(testCase.action, func(t *testing.T) {
			t.Parallel()
			server, captured := newFeishuServer(t, http.StatusOK, `{"code":0,"msg":"success"}`)
			executor := runner.NewRESTExecutor(feishubot.Domain,
				agentteamClient(t, server),
				staticCredentialSource{credentials: unsignedCredentials()},
				feishubot.Handlers())
			_, err := executor.Invoke(context.Background(),
				invocation(testCase.action, testCase.input))
			require.NoErrorf(t, err, "%s must succeed against the double", testCase.action)

			require.Equal(t, http.MethodPost, captured.method, "method")
			require.Equal(t, "/open-apis/bot/v2/hook/token123", captured.path, "path")
			for _, fragment := range testCase.bodyContains {
				require.Containsf(t, captured.body, fragment,
					"%s body must contain %s", testCase.action, fragment)
			}
			for _, key := range testCase.bodyAbsent {
				require.NotContainsf(t, captured.body, `"`+key+`":`,
					"%s body must omit the %q field", testCase.action, key)
			}
		})
	}
}

func unsignedCredentials() runner.Credentials {
	return runner.Credentials{feishubot.APIKeyField: "token123"}
}

// TestFeishuSignatureStampsBodyWithHmac proves the signing secret path: the
// body carries the timestamp and a signature recomputable from that exact
// timestamp.
func TestFeishuSignatureStampsBodyWithHmac(t *testing.T) {
	t.Parallel()

	server, captured := newFeishuServer(t, http.StatusOK, `{"code":0,"msg":"success"}`)
	executor := runner.NewRESTExecutor(feishubot.Domain,
		agentteamClient(t, server),
		staticCredentialSource{credentials: validCredentials()},
		feishubot.Handlers())
	_, err := executor.Invoke(context.Background(),
		invocation(feishubot.ActionSendTextMessage, `{"text":"hi"}`))
	require.NoError(t, err)

	var body struct {
		Timestamp string `json:"timestamp"`
		Sign      string `json:"sign"`
		MsgType   string `json:"msg_type"`
	}
	require.NoError(t, json.Unmarshal([]byte(captured.body), &body))
	require.Equal(t, "text", body.MsgType)
	require.NotEmpty(t, body.Timestamp)
	require.NotEmpty(t, body.Sign)

	mac := hmac.New(sha256.New, []byte(body.Timestamp+"\n"+"secret123"))
	require.Equal(t,
		base64.StdEncoding.EncodeToString(mac.Sum(nil)),
		body.Sign, "signature must be HMAC-SHA256(timestamp+\\n+secret) over empty message")
}

// TestFeishuWebhookURLCredentialRidesThePath proves a full webhook URL
// credential normalizes onto the same wire path as a bare token.
func TestFeishuWebhookURLCredentialRidesThePath(t *testing.T) {
	t.Parallel()

	server, captured := newFeishuServer(t, http.StatusOK, `{"code":0,"msg":"success"}`)
	executor := runner.NewRESTExecutor(feishubot.Domain,
		agentteamClient(t, server),
		staticCredentialSource{credentials: runner.Credentials{
			feishubot.APIKeyField: "https://open.feishu.cn/open-apis/bot/v2/hook/urltoken",
		}},
		feishubot.Handlers())
	_, err := executor.Invoke(context.Background(),
		invocation(feishubot.ActionSendTextMessage, `{"text":"hi"}`))
	require.NoError(t, err)
	require.Equal(t, "/open-apis/bot/v2/hook/urltoken", captured.path)
}
