package service

import (
	"errors"
	"testing"

	"github.com/glebarez/sqlite"
	"git.iioio.com/freefire/jiang13-forum/model"
	"gorm.io/gorm"
)

func setupBountyTestDB(t *testing.T) *gorm.DB {
	t.Helper()
	db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{})
	if err != nil {
		t.Fatal(err)
	}
	if err := db.AutoMigrate(&model.User{}, &model.Post{}, &model.Comment{}, &model.PointLedger{}); err != nil {
		t.Fatal(err)
	}
	prev := model.DB
	model.DB = db
	t.Cleanup(func() { model.DB = prev })
	return db
}

func seedBountyPost(t *testing.T, db *gorm.DB, authorID uint, points int) model.Post {
	t.Helper()
	post := model.Post{
		UserID:       authorID,
		BoardID:      1,
		Title:        "悬赏测试",
		Content:      "内容",
		PostType:     model.PostTypeBounty,
		BountyPoints: points,
		BountyStatus: model.BountyStatusOpen,
		Status:       model.ContentStatusPublished,
	}
	if err := db.Create(&post).Error; err != nil {
		t.Fatal(err)
	}
	return post
}

func seedUser(t *testing.T, db *gorm.DB, id uint, points int) {
	t.Helper()
	u := model.User{
		ID:       id,
		Username: "user" + string(rune('0'+id)),
		Password: "hash",
		Nickname: "测试",
		Points:   points,
	}
	if err := db.Create(&u).Error; err != nil {
		t.Fatal(err)
	}
}

func seedComment(t *testing.T, db *gorm.DB, postID, userID uint, floor int, status string) {
	t.Helper()
	c := model.Comment{
		PostID:  postID,
		UserID:  userID,
		Floor:   floor,
		Content: "回复",
		Status:  status,
	}
	if err := db.Create(&c).Error; err != nil {
		t.Fatal(err)
	}
}

func TestCountEligibleBountyReplies(t *testing.T) {
	db := setupBountyTestDB(t)
	post := seedBountyPost(t, db, 1, 10)

	n, err := CountEligibleBountyReplies(db, post.ID, 1)
	if err != nil || n != 0 {
		t.Fatalf("无回复时期望 0，得到 %d err=%v", n, err)
	}

	seedComment(t, db, post.ID, 1, 1, model.ContentStatusPublished)
	n, err = CountEligibleBountyReplies(db, post.ID, 1)
	if err != nil || n != 0 {
		t.Fatalf("楼主自己的回复不应计入，得到 %d", n)
	}

	seedComment(t, db, post.ID, 2, 2, model.ContentStatusPublished)
	n, err = CountEligibleBountyReplies(db, post.ID, 1)
	if err != nil || n != 1 {
		t.Fatalf("他人 published 回复期望 1，得到 %d", n)
	}

	seedComment(t, db, post.ID, 3, 3, model.ContentStatusPending)
	n, err = CountEligibleBountyReplies(db, post.ID, 1)
	if err != nil || n != 1 {
		t.Fatalf("pending 回复不应增加计数，得到 %d", n)
	}

	seedComment(t, db, post.ID, 0, 4, model.ContentStatusPublished)
	n, err = CountEligibleBountyReplies(db, post.ID, 1)
	if err != nil || n != 2 {
		t.Fatalf("游客回复应计入，得到 %d", n)
	}
}

func TestCanRefundBounty(t *testing.T) {
	db := setupBountyTestDB(t)
	post := seedBountyPost(t, db, 1, 5)

	can, reason := CanRefundBounty(&post, false)
	if !can || reason != "" {
		t.Fatalf("无回复时楼主应可退，can=%v reason=%q", can, reason)
	}

	seedComment(t, db, post.ID, 2, 1, model.ContentStatusPublished)
	can, reason = CanRefundBounty(&post, false)
	if can || reason != bountyRefundBlockReason {
		t.Fatalf("有他人回复时楼主不可退，can=%v reason=%q", can, reason)
	}

	can, reason = CanRefundBounty(&post, true)
	if !can || reason != "" {
		t.Fatalf("管理员应可强制退，can=%v reason=%q", can, reason)
	}
}

func TestRefundBountyBlockedForAuthorWithReplies(t *testing.T) {
	db := setupBountyTestDB(t)
	seedUser(t, db, 1, 0)
	seedUser(t, db, 2, 0)
	post := seedBountyPost(t, db, 1, 8)
	seedComment(t, db, post.ID, 2, 1, model.ContentStatusPublished)

	err := RefundBounty(post.ID, 1, false)
	if !errors.Is(err, ErrBountyRefundBlocked) {
		t.Fatalf("楼主有他人回复时应拒绝退回，err=%v", err)
	}
}

func TestRefundBountyAllowedWithoutReplies(t *testing.T) {
	db := setupBountyTestDB(t)
	seedUser(t, db, 1, 0)
	post := seedBountyPost(t, db, 1, 6)

	if err := RefundBounty(post.ID, 1, false); err != nil {
		t.Fatalf("无回复时楼主应可退回，err=%v", err)
	}
	var updated model.Post
	if err := db.First(&updated, post.ID).Error; err != nil {
		t.Fatal(err)
	}
	if updated.BountyStatus != model.BountyStatusRefunded || updated.BountyPoints != 0 {
		t.Fatalf("状态应为 refunded 且积分为 0，得到 status=%s points=%d", updated.BountyStatus, updated.BountyPoints)
	}
	var author model.User
	if err := db.First(&author, 1).Error; err != nil {
		t.Fatal(err)
	}
	if author.Points != 6 {
		t.Fatalf("楼主应收回 6 积分，余额=%d", author.Points)
	}
}

func TestRefundBountyAdminBypassWithReplies(t *testing.T) {
	db := setupBountyTestDB(t)
	seedUser(t, db, 1, 0)
	seedUser(t, db, 2, 0)
	post := seedBountyPost(t, db, 1, 4)
	seedComment(t, db, post.ID, 2, 1, model.ContentStatusPublished)

	if err := RefundBounty(post.ID, 99, true); err != nil {
		t.Fatalf("管理员应可强制退回，err=%v", err)
	}
}
