package order

import (
	"context"
	"fmt"
	"strings"
	"testing"
	"time"

	"github.com/DATA-DOG/go-sqlmock"
	"github.com/perfect-panel/server/internal/svc"
	modelorder "github.com/perfect-panel/server/internal/model/order"
	modeluser "github.com/perfect-panel/server/internal/model/user"
	"github.com/perfect-panel/server/internal/types"
	"github.com/perfect-panel/server/pkg/constant"
	"github.com/perfect-panel/server/pkg/logger"
	"github.com/perfect-panel/server/pkg/xerr"
	"github.com/pkg/errors"
	"gorm.io/driver/mysql"
	"gorm.io/gorm"
)

func TestOrderStatusName(t *testing.T) {
	tests := map[uint8]string{
		1: "pending",
		2: "paid",
		3: "closed",
		4: "failed",
		5: "finished",
		6: "refunded",
		7: "unknown",
	}

	for input, want := range tests {
		if got := orderStatusName(input); got != want {
			t.Fatalf("status %d: got %q want %q", input, got, want)
		}
	}
}

func TestBuildRefundAuditLog(t *testing.T) {
	logic := &RefundOrderLogic{}
	now := time.Unix(1710000000, 0)
	expireBefore := now.Add(24 * time.Hour)
	expireAfter := now.Add(-time.Second)
	operator := &modeluser.User{
		Id: 100,
		AuthMethods: []modeluser.AuthMethods{
			{AuthIdentifier: "admin@example.com"},
		},
	}
	orderInfo := &modelorder.Order{Id: 10, OrderNo: "ORD-1", Status: orderStatusRefunded}
	userSub := &modeluser.Subscribe{Id: 20, UserId: 30, Status: 3, ExpireTime: expireAfter}
	referer := &modeluser.User{Id: 40}

	got, err := logic.buildRefundAuditLog(
		operator,
		orderInfo,
		userSub,
		referer,
		500,
		"manual refund",
		5,
		1,
		expireBefore,
		900,
		400,
		now,
	)
	if err != nil {
		t.Fatalf("buildRefundAuditLog error: %v", err)
	}
	if got.OrderId != 10 || got.OrderNo != "ORD-1" {
		t.Fatalf("unexpected order info: %+v", got)
	}
	if got.OperatorUserId != 100 || got.OperatorAuthIdentifier != "admin@example.com" {
		t.Fatalf("unexpected operator info: %+v", got)
	}
	if got.TargetUserId != 30 || got.UserSubscribeId != 20 {
		t.Fatalf("unexpected subscription info: %+v", got)
	}
	if got.RefererUserId != 40 || got.CommissionAmount != 500 {
		t.Fatalf("unexpected commission info: %+v", got)
	}
	if got.OrderStatusBefore != 5 || got.OrderStatusAfter != orderStatusRefunded {
		t.Fatalf("unexpected order status: %+v", got)
	}
	if got.SubscribeStatusBefore != 1 || got.SubscribeStatusAfter != 3 {
		t.Fatalf("unexpected subscribe status: %+v", got)
	}
	if got.SubscribeExpireBefore != expireBefore.UnixMilli() || got.SubscribeExpireAfter != expireAfter.UnixMilli() {
		t.Fatalf("unexpected expire transition: %+v", got)
	}
	if got.CommissionBefore != 900 || got.CommissionAfter != 400 {
		t.Fatalf("unexpected commission transition: %+v", got)
	}
}

// TestRefundOrder_RejectsWhenRefundLogExists 验证 HIF-131 / HIF-132 修复：
// 当 system_logs 已存在该订单的 333 退款佣金日志时，再次调用 RefundOrder 必须：
//  1. 返回 OrderAlreadyRefunded 错误码；
//  2. 不再查询 / 锁定 commission 来源（lockCommissionSource 不应触发）；
//  3. 不写入新的 333 日志、不更新 user.commission、不更新 order.status。
//
// 通过 sqlmock 严格定义期望 SQL：只允许出现 BEGIN / SELECT order FOR UPDATE /
// SELECT system_logs（命中 333）/ ROLLBACK，不允许出现 commission 锁/更新/插入。
func TestRefundOrder_RejectsWhenRefundLogExists(t *testing.T) {
	const (
		orderID     = int64(53647)
		orderNo     = "202605301925431836075753253"
		operatorUID = int64(519)
	)

	db, mock, cleanup := newRefundOrderTestDB(t)
	defer cleanup()

	mock.ExpectBegin()
	mock.ExpectQuery("FROM `order`").
		WithArgs(orderID, 1).
		WillReturnRows(sqlmock.NewRows([]string{"id", "order_no", "status", "type", "commission", "user_id"}).
			AddRow(orderID, orderNo, uint8(5), uint8(2), int64(2250), int64(72028)))
	mock.ExpectQuery("FROM `system_logs`").
		WithArgs(uint8(33), fmt.Sprintf(`%%"order_no":"%s"%%`, orderNo)).
		WillReturnRows(sqlmock.NewRows([]string{"id", "content"}).
			AddRow(1, fmt.Sprintf(`{"type":333,"order_no":"%s","amount":-2250,"timestamp":0}`, orderNo)))
	mock.ExpectRollback()

	logic := newTestRefundOrderLogic(t, db, operatorUID)
	err := logic.RefundOrder(&types.RefundOrderRequest{Id: orderID, Reason: "duplicate"})
	if err == nil {
		t.Fatalf("RefundOrder expected error, got nil")
	}
	if !isErrCode(err, xerr.OrderAlreadyRefunded) {
		t.Fatalf("RefundOrder error code = %v, want OrderAlreadyRefunded; raw=%v", errCodeOf(err), err)
	}
	if err := mock.ExpectationsWereMet(); err != nil {
		t.Fatalf("unmet sql expectations: %v", err)
	}
}

// TestRefundOrder_RejectsWhenStatusAlreadyRefunded 覆盖既有 status==6 拒绝路径，
// 确保新增的 333 日志校验不会破坏原有「订单已被标记为退款」短路逻辑。
func TestRefundOrder_RejectsWhenStatusAlreadyRefunded(t *testing.T) {
	const (
		orderID     = int64(1001)
		orderNo     = "ORD-STATUS-6"
		operatorUID = int64(519)
	)

	db, mock, cleanup := newRefundOrderTestDB(t)
	defer cleanup()

	mock.ExpectBegin()
	mock.ExpectQuery("FROM `order`").
		WithArgs(orderID, 1).
		WillReturnRows(sqlmock.NewRows([]string{"id", "order_no", "status"}).
			AddRow(orderID, orderNo, uint8(orderStatusRefunded)))
	mock.ExpectRollback()

	logic := newTestRefundOrderLogic(t, db, operatorUID)
	err := logic.RefundOrder(&types.RefundOrderRequest{Id: orderID})
	if !isErrCode(err, xerr.OrderAlreadyRefunded) {
		t.Fatalf("RefundOrder error code = %v, want OrderAlreadyRefunded; raw=%v", errCodeOf(err), err)
	}
	if err := mock.ExpectationsWereMet(); err != nil {
		t.Fatalf("unmet sql expectations: %v", err)
	}
}

// TestRefundOrder_RejectsWhenStatusNotRefundable 覆盖非 2/5 状态短路。
func TestRefundOrder_RejectsWhenStatusNotRefundable(t *testing.T) {
	const (
		orderID     = int64(1002)
		orderNo     = "ORD-STATUS-1"
		operatorUID = int64(519)
	)

	db, mock, cleanup := newRefundOrderTestDB(t)
	defer cleanup()

	mock.ExpectBegin()
	mock.ExpectQuery("FROM `order`").
		WithArgs(orderID, 1).
		WillReturnRows(sqlmock.NewRows([]string{"id", "order_no", "status"}).
			AddRow(orderID, orderNo, uint8(1)))
	mock.ExpectRollback()

	logic := newTestRefundOrderLogic(t, db, operatorUID)
	err := logic.RefundOrder(&types.RefundOrderRequest{Id: orderID})
	if !isErrCode(err, xerr.OrderStatusError) {
		t.Fatalf("RefundOrder error code = %v, want OrderStatusError; raw=%v", errCodeOf(err), err)
	}
	if err := mock.ExpectationsWereMet(); err != nil {
		t.Fatalf("unmet sql expectations: %v", err)
	}
}

func newRefundOrderTestDB(t *testing.T) (*gorm.DB, sqlmock.Sqlmock, func()) {
	t.Helper()

	sqlDB, mock, err := sqlmock.New(sqlmock.QueryMatcherOption(sqlmock.QueryMatcherFunc(func(expectedSQL, actualSQL string) error {
		if strings.Contains(actualSQL, expectedSQL) {
			return nil
		}
		return fmt.Errorf("actual sql %q does not contain %q", actualSQL, expectedSQL)
	})))
	if err != nil {
		t.Fatalf("create sqlmock: %v", err)
	}

	db, err := gorm.Open(mysql.New(mysql.Config{Conn: sqlDB, SkipInitializeWithVersion: true}), &gorm.Config{})
	if err != nil {
		_ = sqlDB.Close()
		t.Fatalf("open gorm db: %v", err)
	}

	return db, mock, func() {
		_ = sqlDB.Close()
	}
}

func newTestRefundOrderLogic(t *testing.T, db *gorm.DB, operatorID int64) *RefundOrderLogic {
	t.Helper()
	ctx := context.WithValue(context.Background(), constant.CtxKeyUser, &modeluser.User{Id: operatorID})
	return &RefundOrderLogic{
		Logger: logger.WithContext(ctx),
		ctx:    ctx,
		svcCtx: &svc.ServiceContext{DB: db},
	}
}

// errCodeOf / isErrCode 用于绕开 wrapped error 检查内层 xerr 错误码。
func errCodeOf(err error) uint32 {
	if err == nil {
		return 0
	}
	type coder interface {
		GetErrCode() uint32
	}
	cause := errors.Cause(err)
	if c, ok := cause.(coder); ok {
		return c.GetErrCode()
	}
	return 0
}

func isErrCode(err error, code uint32) bool {
	return errCodeOf(err) == code
}
