package workbench

import (
	"encoding/json"
	"testing"
)

// TestInboxPlanMapsEvents 覆盖 inboxPlan 纯函数:每个支持的事件类型都要产出
// 预期类别 / 收件人类别 / 文案关键词,未知事件返回 nil。
func TestInboxPlanMapsEvents(t *testing.T) {
	payload := func(values map[string]any) json.RawMessage {
		encoded, err := json.Marshal(values)
		if err != nil {
			t.Fatal(err)
		}
		return encoded
	}

	cases := []struct {
		name         string
		eventType    string
		values       map[string]any
		wantKind     string // recipient_kind
		wantCategory string
		wantTitle    string
		wantUserID   string
		wantAll      bool
		wantRequest  bool
	}{
		{name: "model_access.requested 通知全部管理员审批", eventType: "model_access.requested", values: map[string]any{"model": "gpt-5"}, wantKind: "admin", wantCategory: "approval", wantTitle: "新的模型访问申请", wantAll: true},
		{name: "model_access.decided 已批准回执给申请用户", eventType: "model_access.decided", values: map[string]any{"status": "approved"}, wantKind: "portal", wantCategory: "approval", wantTitle: "模型申请已处理", wantRequest: true},
		{name: "model_access.decided 已驳回文案", eventType: "model_access.decided", values: map[string]any{"status": "rejected"}, wantKind: "portal", wantCategory: "approval", wantTitle: "模型申请已处理", wantRequest: true},
		{name: "marketplace.installed 发给安装用户", eventType: "marketplace.installed", values: map[string]any{"code": "report-bot", "portal_user_id": "11111111-1111-1111-1111-111111111111"}, wantKind: "portal", wantCategory: "resource", wantTitle: "资源已安装", wantUserID: "11111111-1111-1111-1111-111111111111"},
		{name: "knowledge_document.ready 发给执行管理员", eventType: "knowledge_document.ready", values: map[string]any{"chunk_count": "12", "actor_id": "22222222-2222-2222-2222-222222222222"}, wantKind: "admin", wantCategory: "system", wantTitle: "知识文档已入库", wantUserID: "22222222-2222-2222-2222-222222222222"},
		{name: "knowledge_document.reprocessed 发给执行管理员", eventType: "knowledge_document.reprocessed", values: map[string]any{"actor_id": "22222222-2222-2222-2222-222222222222"}, wantKind: "admin", wantCategory: "system", wantTitle: "知识文档已重新处理", wantUserID: "22222222-2222-2222-2222-222222222222"},
		{name: "knowledge_document.embedding_failed 降级提示", eventType: "knowledge_document.embedding_failed", values: map[string]any{"actor_id": "22222222-2222-2222-2222-222222222222"}, wantKind: "admin", wantCategory: "system", wantTitle: "知识文档向量化失败", wantUserID: "22222222-2222-2222-2222-222222222222"},
		{name: "scheduled_task.completed 发给创建者", eventType: "scheduled_task.completed", values: map[string]any{"task_code": "daily-report", "actor_id": "33333333-3333-3333-3333-333333333333"}, wantKind: "admin", wantCategory: "task_result", wantTitle: "定时任务已执行", wantUserID: "33333333-3333-3333-3333-333333333333"},
		{name: "scheduled_task.failed 发给创建者", eventType: "scheduled_task.failed", values: map[string]any{"task_code": "daily-report", "error": "timeout", "actor_id": "33333333-3333-3333-3333-333333333333"}, wantKind: "admin", wantCategory: "task_result", wantTitle: "定时任务执行失败", wantUserID: "33333333-3333-3333-3333-333333333333"},
	}

	for _, tc := range cases {
		t.Run(tc.name, func(t *testing.T) {
			drafts := inboxPlan(tc.eventType, payload(tc.values))
			if len(drafts) != 1 {
				t.Fatalf("expected exactly one draft, got %d", len(drafts))
			}
			draft := drafts[0]
			if draft.RecipientKind != tc.wantKind {
				t.Errorf("recipient_kind = %q, want %q", draft.RecipientKind, tc.wantKind)
			}
			if draft.Category != tc.wantCategory {
				t.Errorf("category = %q, want %q", draft.Category, tc.wantCategory)
			}
			if draft.Title != tc.wantTitle {
				t.Errorf("title = %q, want %q", draft.Title, tc.wantTitle)
			}
			if draft.UserID != tc.wantUserID {
				t.Errorf("user_id = %q, want %q", draft.UserID, tc.wantUserID)
			}
			if draft.AllAdmins != tc.wantAll {
				t.Errorf("all_admins = %v, want %v", draft.AllAdmins, tc.wantAll)
			}
			if draft.RequestUser != tc.wantRequest {
				t.Errorf("request_user = %v, want %v", draft.RequestUser, tc.wantRequest)
			}
		})
	}

	if drafts := inboxPlan("some.unknown.event", payload(map[string]any{})); drafts != nil {
		t.Fatalf("unknown event should map to no drafts, got %+v", drafts)
	}
}

// TestInboxPlanModelAccessRejectedBody 校验驳回与批准的不同正文文案。
func TestInboxPlanModelAccessRejectedBody(t *testing.T) {
	values := func(status string) json.RawMessage {
		encoded, _ := json.Marshal(map[string]any{"status": status})
		return encoded
	}
	approved := inboxPlan("model_access.decided", values("approved"))
	rejected := inboxPlan("model_access.decided", values("rejected"))
	if !contains(approved[0].Body, "已批准") {
		t.Errorf("approved body should mention 已批准, got %q", approved[0].Body)
	}
	if !contains(rejected[0].Body, "已驳回") {
		t.Errorf("rejected body should mention 已驳回, got %q", rejected[0].Body)
	}
}

func contains(haystack, needle string) bool {
	for i := 0; i+len(needle) <= len(haystack); i++ {
		if haystack[i:i+len(needle)] == needle {
			return true
		}
	}
	return false
}

// TestPayloadValue 校验 payloadValue 对字符串与数字两类取值的兼容(事件载荷
// 中数字可能被 JSON 解码为 float64)。
func TestPayloadValue(t *testing.T) {
	payload := json.RawMessage(`{"model":"gpt-5","chunk_count":12,"active":true}`)
	if got := payloadValue(payload, "model"); got != "gpt-5" {
		t.Errorf("string key = %q, want gpt-5", got)
	}
	if got := payloadValue(payload, "chunk_count"); got != "12" {
		t.Errorf("numeric key = %q, want 12", got)
	}
	if got := payloadValue(payload, "missing"); got != "" {
		t.Errorf("missing key = %q, want empty", got)
	}
}

func TestValidInboxLink(t *testing.T) {
	cases := map[string]bool{
		"": true, "/portal/inbox": true, "https://example.com/notice": true,
		"http://example.com": true, "javascript:alert(1)": false,
		"data:text/html,x": false, "//example.com/path": false, "portal/inbox": false,
	}
	for link, want := range cases {
		if got := validInboxLink(link); got != want {
			t.Errorf("validInboxLink(%q) = %v, want %v", link, got, want)
		}
	}
}
