package chat

import (
	"errors"
	"strings"
	"testing"
	"time"

	"hfb_sys/backend/internal/model"

	"gorm.io/driver/sqlite"
	"gorm.io/gorm"
	"gorm.io/gorm/logger"
)

func setupQrCodeTestDB(t *testing.T) *gorm.DB {
	t.Helper()
	db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{
		Logger: logger.Default.LogMode(logger.Silent),
	})
	if err != nil {
		t.Fatalf("无法创建测试数据库: %v", err)
	}
	if err := db.AutoMigrate(
		&model.ChatQrCode{},
		&model.ChatConversation{},
		&model.ChatParticipant{},
		&model.ChatMessage{},
		&model.ChatQrCodeDeliveryTask{},
		&model.ChatSupportGroup{},
		&model.ChatSupportGroupMember{},
		&model.AdminUser{},
		&model.AdminUserRole{},
		&model.Role{},
		&model.SystemConfig{},
	); err != nil {
		t.Fatalf("数据库迁移失败: %v", err)
	}
	return db
}

func TestGetQrCodeOCRConfigReadsSystemConfig(t *testing.T) {
	db := setupQrCodeTestDB(t)
	repo := NewRepository(db, nil)
	rows := []model.SystemConfig{
		{Key: qrCodeOCRTokenConfigKey, Value: "test-token"},
		{Key: qrCodeOCRJobURLConfigKey, Value: "https://example.test/ocr/jobs"},
		{Key: qrCodeOCRModelConfigKey, Value: "PaddleOCR-Test"},
	}
	if err := db.Create(&rows).Error; err != nil {
		t.Fatalf("创建系统配置失败: %v", err)
	}

	config, err := repo.GetQrCodeOCRConfig(t.Context())
	if err != nil {
		t.Fatalf("读取 OCR 配置失败: %v", err)
	}
	if config.Token != "test-token" {
		t.Fatalf("token = %q", config.Token)
	}
	if config.JobURL != "https://example.test/ocr/jobs" {
		t.Fatalf("job_url = %q", config.JobURL)
	}
	if config.Model != "PaddleOCR-Test" {
		t.Fatalf("model = %q", config.Model)
	}
}

func TestListQrCodesIncludesBoundConversationTitle(t *testing.T) {
	db := setupQrCodeTestDB(t)
	repo := NewRepository(db, nil)
	conversation := model.ChatConversation{
		Title:  "账号群 L202606180001",
		Type:   ConversationTypeListingGroup,
		Status: "active",
	}
	if err := db.Create(&conversation).Error; err != nil {
		t.Fatalf("创建群聊失败: %v", err)
	}

	qrcode := model.ChatQrCode{
		ImageURL:       "/api/files/object?key=qrcode/group.png",
		GroupName:      "王大锤-鼠鼠跑刀一群",
		Status:         QrCodeStatusUsed,
		ConversationID: &conversation.ID,
		CreatedBy:      1,
	}
	if err := db.Create(&qrcode).Error; err != nil {
		t.Fatalf("创建二维码失败: %v", err)
	}

	items, total, err := repo.ListQrCodes(t.Context(), QrCodeListRequest{Page: 1, Limit: 20})
	if err != nil {
		t.Fatalf("查询二维码失败: %v", err)
	}
	if total != 1 || len(items) != 1 {
		t.Fatalf("total = %d, len = %d, want 1", total, len(items))
	}
	if items[0].GroupName != "王大锤-鼠鼠跑刀一群" {
		t.Fatalf("群名 = %q", items[0].GroupName)
	}
	if items[0].BoundConversationTitle != "账号群 L202606180001" {
		t.Fatalf("绑定群名 = %q", items[0].BoundConversationTitle)
	}
}

func TestParseQrCodeGroupNameCandidates(t *testing.T) {
	rawText := strings.Join([]string{
		"使用微信或企业微信扫码加入",
		"王大锤-鼠鼠跑刀一群",
		"这二维码7天内有效",
	}, "\n")

	candidates := parseQrCodeGroupNameCandidates(rawText)
	if len(candidates) == 0 {
		t.Fatal("未解析出群名候选")
	}
	if candidates[0] != "王大锤-鼠鼠跑刀一群" {
		t.Fatalf("首个候选 = %q", candidates[0])
	}
}

func TestUpdateQrCodeRejectsUsedToUnused(t *testing.T) {
	db := setupQrCodeTestDB(t)
	repo := NewRepository(db, nil)
	now := time.Date(2026, 6, 18, 12, 0, 0, 0, time.UTC)
	conversationID := uint64(1001)
	qrcode := model.ChatQrCode{
		ImageURL:       "/api/files/object?key=qrcode/used.png",
		Status:         QrCodeStatusUsed,
		ConversationID: &conversationID,
		UsedAt:         &now,
		CreatedBy:      1,
	}
	if err := db.Create(&qrcode).Error; err != nil {
		t.Fatalf("创建二维码失败: %v", err)
	}

	status := QrCodeStatusUnused
	err := repo.UpdateQrCode(t.Context(), qrcode.ID, UpdateQrCodeRequest{Status: &status})
	if !errors.Is(err, ErrQrCodeCannotReenable) {
		t.Fatalf("error = %v, want ErrQrCodeCannotReenable", err)
	}

	var saved model.ChatQrCode
	if err := db.First(&saved, qrcode.ID).Error; err != nil {
		t.Fatalf("查询二维码失败: %v", err)
	}
	if saved.Status != QrCodeStatusUsed {
		t.Fatalf("二维码状态 = %q, want %q", saved.Status, QrCodeStatusUsed)
	}
}

func TestUpdateQrCodeRejectsIssuedDisabledToUnused(t *testing.T) {
	db := setupQrCodeTestDB(t)
	repo := NewRepository(db, nil)
	conversationID := uint64(1002)
	qrcode := model.ChatQrCode{
		ImageURL:       "/api/files/object?key=qrcode/disabled.png",
		Status:         QrCodeStatusDisabled,
		ConversationID: &conversationID,
		CreatedBy:      1,
	}
	if err := db.Create(&qrcode).Error; err != nil {
		t.Fatalf("创建二维码失败: %v", err)
	}

	status := QrCodeStatusUnused
	err := repo.UpdateQrCode(t.Context(), qrcode.ID, UpdateQrCodeRequest{Status: &status})
	if !errors.Is(err, ErrQrCodeCannotReenable) {
		t.Fatalf("error = %v, want ErrQrCodeCannotReenable", err)
	}
}

func TestUpdateQrCodeAllowsUnusedToDisabled(t *testing.T) {
	db := setupQrCodeTestDB(t)
	repo := NewRepository(db, nil)
	qrcode := model.ChatQrCode{
		ImageURL:  "/api/files/object?key=qrcode/unused.png",
		Status:    QrCodeStatusUnused,
		CreatedBy: 1,
	}
	if err := db.Create(&qrcode).Error; err != nil {
		t.Fatalf("创建二维码失败: %v", err)
	}

	status := QrCodeStatusDisabled
	if err := repo.UpdateQrCode(t.Context(), qrcode.ID, UpdateQrCodeRequest{Status: &status}); err != nil {
		t.Fatalf("更新二维码失败: %v", err)
	}

	var saved model.ChatQrCode
	if err := db.First(&saved, qrcode.ID).Error; err != nil {
		t.Fatalf("查询二维码失败: %v", err)
	}
	if saved.Status != QrCodeStatusDisabled {
		t.Fatalf("二维码状态 = %q, want %q", saved.Status, QrCodeStatusDisabled)
	}
}

func TestUpdateQrCodeGroupNameAndRenameFlag(t *testing.T) {
	db := setupQrCodeTestDB(t)
	repo := NewRepository(db, nil)
	qrcode := model.ChatQrCode{
		ImageURL:  "/api/files/object?key=qrcode/unused.png",
		Status:    QrCodeStatusUnused,
		CreatedBy: 1,
	}
	if err := db.Create(&qrcode).Error; err != nil {
		t.Fatalf("创建二维码失败: %v", err)
	}

	groupName := "王大锤-鼠鼠跑刀一群"
	renamed := true
	if err := repo.UpdateQrCode(t.Context(), qrcode.ID, UpdateQrCodeRequest{
		GroupName:    &groupName,
		WecomRenamed: &renamed,
	}); err != nil {
		t.Fatalf("更新二维码失败: %v", err)
	}

	var saved model.ChatQrCode
	if err := db.First(&saved, qrcode.ID).Error; err != nil {
		t.Fatalf("查询二维码失败: %v", err)
	}
	if saved.GroupName != groupName {
		t.Fatalf("群名 = %q, want %q", saved.GroupName, groupName)
	}
	if !saved.WecomRenamed {
		t.Fatal("企微改名标记未保存")
	}
}

func TestDeleteQrCodeAllowsIssuedQrCode(t *testing.T) {
	db := setupQrCodeTestDB(t)
	repo := NewRepository(db, nil)
	now := time.Date(2026, 6, 19, 17, 8, 0, 0, time.UTC)
	conversationID := uint64(1003)
	qrcode := model.ChatQrCode{
		ImageURL:       "/api/files/object?key=qrcode/issued.png",
		Status:         QrCodeStatusUsed,
		ConversationID: &conversationID,
		UsedAt:         &now,
		CreatedBy:      1,
	}
	if err := db.Create(&qrcode).Error; err != nil {
		t.Fatalf("创建二维码失败: %v", err)
	}

	if err := repo.DeleteQrCode(t.Context(), qrcode.ID); err != nil {
		t.Fatalf("删除已发放二维码失败: %v", err)
	}

	var count int64
	if err := db.Model(&model.ChatQrCode{}).Where("id = ?", qrcode.ID).Count(&count).Error; err != nil {
		t.Fatalf("统计二维码失败: %v", err)
	}
	if count != 0 {
		t.Fatalf("二维码数量 = %d, want 0", count)
	}
}

func TestBatchDeleteQrCodes(t *testing.T) {
	db := setupQrCodeTestDB(t)
	repo := NewRepository(db, nil)
	now := time.Date(2026, 6, 19, 17, 8, 0, 0, time.UTC)
	conversationID := uint64(1004)
	qrcodes := []model.ChatQrCode{
		{
			ImageURL:  "/api/files/object?key=qrcode/unused.png",
			Status:    QrCodeStatusUnused,
			CreatedBy: 1,
		},
		{
			ImageURL:       "/api/files/object?key=qrcode/used.png",
			Status:         QrCodeStatusUsed,
			ConversationID: &conversationID,
			UsedAt:         &now,
			CreatedBy:      1,
		},
		{
			ImageURL:  "/api/files/object?key=qrcode/keep.png",
			Status:    QrCodeStatusDisabled,
			CreatedBy: 1,
		},
	}
	if err := db.Create(&qrcodes).Error; err != nil {
		t.Fatalf("创建二维码失败: %v", err)
	}

	deleted, err := repo.BatchDeleteQrCodes(t.Context(), []uint64{qrcodes[0].ID, qrcodes[1].ID})
	if err != nil {
		t.Fatalf("批量删除二维码失败: %v", err)
	}
	if deleted != 2 {
		t.Fatalf("删除数量 = %d, want 2", deleted)
	}

	var remaining []model.ChatQrCode
	if err := db.Order("id ASC").Find(&remaining).Error; err != nil {
		t.Fatalf("查询剩余二维码失败: %v", err)
	}
	if len(remaining) != 1 || remaining[0].ID != qrcodes[2].ID {
		t.Fatalf("剩余二维码 = %+v, want id %d", remaining, qrcodes[2].ID)
	}
}

func TestEnsureListingConversationCreatesQrCodeDeliveryTaskWhenStockEmpty(t *testing.T) {
	db := setupQrCodeTestDB(t)
	listing := model.RentalListing{
		ID:        101,
		ListingNo: "L202606260001",
		OwnerID:   10,
	}

	if err := db.Transaction(func(tx *gorm.DB) error {
		_, err := EnsureListingConversation(tx, listing, 0)
		return err
	}); err != nil {
		t.Fatalf("建发布群失败: %v", err)
	}

	var task model.ChatQrCodeDeliveryTask
	if err := db.First(&task).Error; err != nil {
		t.Fatalf("未创建补发任务: %v", err)
	}
	if task.Status != "pending" {
		t.Fatalf("补发任务状态 = %q, want pending", task.Status)
	}
}

func TestBatchCreateQrCodeDeliversPendingTask(t *testing.T) {
	db := setupQrCodeTestDB(t)
	repo := NewRepository(db, nil)
	conversation := model.ChatConversation{
		Title:  "账号群 L202606260002",
		Type:   ConversationTypeListingGroup,
		Status: "active",
	}
	if err := db.Create(&conversation).Error; err != nil {
		t.Fatalf("创建群聊失败: %v", err)
	}
	task := model.ChatQrCodeDeliveryTask{
		ConversationID: conversation.ID,
		Status:         "pending",
	}
	if err := db.Create(&task).Error; err != nil {
		t.Fatalf("创建补发任务失败: %v", err)
	}

	_, err := repo.BatchCreateQrCode(t.Context(), 1, BatchCreateQrCodeRequest{
		Items: []CreateQrCodeRequest{
			{ImageURL: "/api/files/object?key=qrcode/replenish.png", GroupName: "补发群"},
		},
	})
	if err != nil {
		t.Fatalf("批量创建二维码失败: %v", err)
	}

	var savedTask model.ChatQrCodeDeliveryTask
	if err := db.First(&savedTask, task.ID).Error; err != nil {
		t.Fatalf("查询补发任务失败: %v", err)
	}
	if savedTask.Status != "sent" {
		t.Fatalf("补发任务状态 = %q, want sent", savedTask.Status)
	}
	if savedTask.QrCodeID == nil {
		t.Fatal("补发任务未记录二维码 ID")
	}
	if savedTask.SentAt == nil {
		t.Fatal("补发任务未记录 sent_at")
	}

	var qrcode model.ChatQrCode
	if err := db.First(&qrcode, *savedTask.QrCodeID).Error; err != nil {
		t.Fatalf("查询二维码失败: %v", err)
	}
	if qrcode.Status != QrCodeStatusUsed {
		t.Fatalf("二维码状态 = %q, want used", qrcode.Status)
	}
	if qrcode.ConversationID == nil || *qrcode.ConversationID != conversation.ID {
		t.Fatalf("二维码绑定会话 = %v, want %d", qrcode.ConversationID, conversation.ID)
	}

	var message model.ChatMessage
	if err := db.Where("conversation_id = ? AND content_type = ?", conversation.ID, "image").First(&message).Error; err != nil {
		t.Fatalf("未生成二维码图片消息: %v", err)
	}
}
