package payment

import (
	"context"
	"encoding/json"
	"testing"
	"time"

	"hfb_sys/backend/internal/model"
	ordermodule "hfb_sys/backend/internal/modules/order"

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

// setupPaymentTestDB 创建测试数据库
func setupPaymentTestDB(t *testing.T) *gorm.DB {
	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.PaymentOrder{},
		&model.RentalOrder{},
		&model.RentalListing{},
		&model.GameAccount{},
		&model.User{},
		&model.ChatConversation{},
		&model.ChatParticipant{},
		&model.ChatMessage{},
		&model.Notification{},
	); err != nil {
		t.Fatalf("数据库迁移失败: %v", err)
	}

	return db
}

type payableOrderFixture struct {
	Owner   model.User
	Renter  model.User
	Account model.GameAccount
	Listing model.RentalListing
	Order   model.RentalOrder
	Payment model.PaymentOrder
}

func createPayableOrderFixture(t *testing.T, db *gorm.DB, suffix string, createPayment bool) payableOrderFixture {
	t.Helper()
	f := payableOrderFixture{
		Owner:  model.User{Phone: "13800001" + suffix},
		Renter: model.User{Phone: "13900001" + suffix},
	}
	if err := db.Create(&f.Owner).Error; err != nil {
		t.Fatalf("create owner failed: %v", err)
	}
	if err := db.Create(&f.Renter).Error; err != nil {
		t.Fatalf("create renter failed: %v", err)
	}
	f.Account = model.GameAccount{
		OwnerID:       f.Owner.ID,
		ServerRegion:  "国服",
		LoginPlatform: "steam",
		Title:         "测试账号",
		Status:        "published",
	}
	if err := db.Create(&f.Account).Error; err != nil {
		t.Fatalf("create account failed: %v", err)
	}
	f.Listing = model.RentalListing{
		ListingNo:     "LST20260614" + suffix,
		OwnerID:       f.Owner.ID,
		AccountID:     f.Account.ID,
		Status:        "published",
		ReviewStatus:  "approved",
		InTransaction: true,
		PriceCent:     1000,
	}
	if err := db.Create(&f.Listing).Error; err != nil {
		t.Fatalf("create listing failed: %v", err)
	}
	f.Order = model.RentalOrder{
		OrderNo:        "ORD20260614" + suffix,
		ListingID:      f.Listing.ID,
		AccountID:      f.Account.ID,
		OwnerID:        f.Owner.ID,
		RenterID:       f.Renter.ID,
		RentAmountCent: 1000,
		Status:         "pending_payment",
		HandoffStatus:  "none",
	}
	if err := db.Create(&f.Order).Error; err != nil {
		t.Fatalf("create order failed: %v", err)
	}
	f.Payment = model.PaymentOrder{
		PaymentNo:       "PAY20260614" + suffix,
		OrderID:         f.Order.ID,
		OrderNo:         f.Order.OrderNo,
		UserID:          f.Renter.ID,
		Provider:        "mock",
		ThirdOrderID:    "PAY20260614" + suffix,
		ProviderOrderID: "MOCKPAY20260614" + suffix,
		AmountCent:      1000,
		BizType:         "order_pay",
		Status:          "paying",
	}
	if createPayment {
		if err := db.Create(&f.Payment).Error; err != nil {
			t.Fatalf("create payment failed: %v", err)
		}
	}
	return f
}

func TestMarkRefundFailedPersistsRawRequest(t *testing.T) {
	db := setupPaymentTestDB(t)
	f := createPayableOrderFixture(t, db, "RAWREQ", true)
	refund := model.PaymentOrder{
		PaymentNo:       "PAYREFRAWREQ",
		OrderID:         f.Order.ID,
		OrderNo:         f.Order.OrderNo,
		UserID:          f.Renter.ID,
		Provider:        "lakala",
		MerchantID:      "merchant-1",
		ThirdOrderID:    "REFRAWREQ",
		ProviderOrderID: "",
		AmountCent:      99,
		BizType:         "checkout_refund",
		Status:          "refunding",
	}
	if err := db.Create(&refund).Error; err != nil {
		t.Fatalf("create refund payment failed: %v", err)
	}

	repo := NewRepository(db, nil, nil)
	rawRequest := map[string]string{
		"merchant_no":     "merchant-1",
		"out_trade_no":    "REFRAWREQ",
		"refund_amount":   "99",
		"origin_trade_no": "TRADE123",
	}
	err := repo.markRefundFailed(context.Background(), refund.ID, f.Order.ID, refund.AmountCent, map[string]string{"error": "lakala http status 400"}, rawRequest)
	if err != nil {
		t.Fatalf("mark refund failed: %v", err)
	}

	var saved model.PaymentOrder
	if err := db.First(&saved, refund.ID).Error; err != nil {
		t.Fatalf("find refund payment failed: %v", err)
	}
	var got map[string]string
	if err := json.Unmarshal(saved.RawRequest, &got); err != nil {
		t.Fatalf("decode raw_request: %v", err)
	}
	for key, want := range rawRequest {
		if got[key] != want {
			t.Fatalf("raw_request[%s] = %q, want %q; full=%v", key, got[key], want, got)
		}
	}
}

// TestCanReusePaymentWithSameProvider 测试相同渠道可复用
func TestCanReusePaymentWithSameProvider(t *testing.T) {
	payment := model.PaymentOrder{
		Status:     "paying",
		Provider:   "lakala",
		MerchantID: "M123",
	}

	config := runtimePaymentConfig{
		Provider:   "lakala",
		MerchantID: "M123",
	}

	if !canReuseOrderPayment(payment, config) {
		t.Fatal("should reuse payment with same provider and merchant")
	}
}

// TestCannotReusePaymentWithDifferentProvider 测试不同渠道不可复用
func TestCannotReusePaymentWithDifferentProvider(t *testing.T) {
	payment := model.PaymentOrder{
		Status:     "paying",
		Provider:   "lakala",
		MerchantID: "M123",
	}

	config := runtimePaymentConfig{
		Provider:   "leshua", // 不同渠道
		MerchantID: "M456",
	}

	if canReuseOrderPayment(payment, config) {
		t.Fatal("should not reuse payment with different provider")
	}
}

// TestPaymentStatusTransitions 测试支付单状态转换
func TestPaymentStatusTransitionsValid(t *testing.T) {
	validTransitions := map[string][]string{
		"pending":  {"paying", "closed"},
		"paying":   {"paid", "failed", "closed"},
		"paid":     {"refunding", "refunded"},
		"failed":   {}, // 终态
		"closed":   {}, // 终态
		"refunded": {}, // 终态
	}

	for from, toList := range validTransitions {
		if from == "" {
			t.Fatal("payment status should not be empty")
		}
		for _, to := range toList {
			if to == "" {
				t.Fatal("transition target should not be empty")
			}
		}
	}
}

// TestRefundBizTypeCoverage 测试退款业务类型覆盖
func TestRefundBizTypeCoverage(t *testing.T) {
	expected := []string{
		"cancel_refund",
		"admin_close_refund",
		"admin_refund",
		"checkout_refund",
		"deposit_refund",
		"rent_refund",
		"arbitration_refund",
	}

	if len(refundBizTypes) != len(expected) {
		t.Fatalf("refundBizTypes count = %d, want %d", len(refundBizTypes), len(expected))
	}

	// 验证每个预期类型都存在
	typeMap := make(map[string]bool)
	for _, bizType := range refundBizTypes {
		typeMap[bizType] = true
	}

	for _, exp := range expected {
		if !typeMap[exp] {
			t.Fatalf("missing refund biz type: %s", exp)
		}
	}
}

// TestPaymentAmountValidation 测试支付金额验证
func TestPaymentAmountMustBePositive(t *testing.T) {
	testCases := []struct {
		name      string
		amount    int64
		wantValid bool
	}{
		{"正数金额", 10000, true},
		{"零金额", 0, false},
		{"负数金额", -1000, false},
		{"最小金额", 1, true},
	}

	for _, tc := range testCases {
		t.Run(tc.name, func(t *testing.T) {
			isValid := tc.amount > 0
			if isValid != tc.wantValid {
				t.Fatalf("amount %d validation = %v, want %v", tc.amount, isValid, tc.wantValid)
			}
		})
	}
}

// TestPaymentProviderValidation 测试支付渠道验证
func TestPaymentProviderMustNotBeEmpty(t *testing.T) {
	testCases := []struct {
		name      string
		provider  string
		wantValid bool
	}{
		{"lakala 渠道", "lakala", true},
		{"leshua 渠道", "leshua", true},
		{"mock 渠道", "mock", true},
		{"空渠道", "", false},
	}

	for _, tc := range testCases {
		t.Run(tc.name, func(t *testing.T) {
			isValid := tc.provider != ""
			if isValid != tc.wantValid {
				t.Fatalf("provider %q validation = %v, want %v", tc.provider, isValid, tc.wantValid)
			}
		})
	}
}

// TestRuntimeConfigIsMockMode 测试运行时配置 mock 模式
func TestRuntimeConfigIsMockModeTrue(t *testing.T) {
	config := runtimePaymentConfig{
		Provider: "mock",
	}

	if !config.isMockMode() {
		t.Fatal("should be mock mode when provider is 'mock'")
	}
}

func TestRuntimeConfigIsMockModeFalse(t *testing.T) {
	providers := []string{"lakala", "leshua", "alipay", "wechat"}

	for _, provider := range providers {
		config := runtimePaymentConfig{
			Provider: provider,
		}

		if config.isMockMode() {
			t.Fatalf("should not be mock mode when provider is %q", provider)
		}
	}
}

// TestChannelSourceValidation 测试渠道来源验证
func TestChannelSourceMustBeValid(t *testing.T) {
	validSources := []string{
		channelSourceCreate,
		channelSourceQuery,
		channelSourceNotify,
		channelSourceMock,
	}

	expectedValues := []string{"create", "query", "notify", "mock"}

	for i, source := range validSources {
		if source != expectedValues[i] {
			t.Fatalf("channelSource[%d] = %q, want %q", i, source, expectedValues[i])
		}
	}
}

// TestPaymentOrderFields 测试支付单字段完整性
func TestPaymentOrderRequiredFields(t *testing.T) {
	payment := model.PaymentOrder{
		PaymentNo:  "PAY123456",
		OrderNo:    "ORD123456",
		OrderID:    100,
		UserID:     1,
		AmountCent: 10000,
		Status:     "paying",
		Provider:   "lakala",
		MerchantID: "M123",
		BizType:    "order_pay",
	}

	// 验证必填字段
	if payment.PaymentNo == "" {
		t.Fatal("PaymentNo should not be empty")
	}
	if payment.OrderNo == "" {
		t.Fatal("OrderNo should not be empty")
	}
	if payment.OrderID == 0 {
		t.Fatal("OrderID should not be zero")
	}
	if payment.UserID == 0 {
		t.Fatal("UserID should not be zero")
	}
	if payment.AmountCent <= 0 {
		t.Fatal("AmountCent should be positive")
	}
	if payment.Status == "" {
		t.Fatal("Status should not be empty")
	}
	if payment.Provider == "" {
		t.Fatal("Provider should not be empty")
	}
}

// TestRefundAmountValidation 测试退款金额验证
func TestRefundAmountMustNotExceedOriginal(t *testing.T) {
	testCases := []struct {
		name           string
		originalAmount int64
		refundAmount   int64
		wantValid      bool
	}{
		{"部分退款", 10000, 5000, true},
		{"全额退款", 10000, 10000, true},
		{"超额退款", 10000, 15000, false},
		{"零退款", 10000, 0, false},
		{"负数退款", 10000, -1000, false},
	}

	for _, tc := range testCases {
		t.Run(tc.name, func(t *testing.T) {
			isValid := tc.refundAmount > 0 && tc.refundAmount <= tc.originalAmount
			if isValid != tc.wantValid {
				t.Fatalf("refund %d from %d validation = %v, want %v",
					tc.refundAmount, tc.originalAmount, isValid, tc.wantValid)
			}
		})
	}
}

// TestPaymentNotifyResultStructure 测试支付回调结果结构
func TestNotifyResultHasRequiredFields(t *testing.T) {
	result := NotifyResult{
		OK:      true,
		Message: "payment success",
	}

	if result.Message == "" {
		t.Fatal("NotifyResult.Message should not be empty")
	}
}

func TestStartRefundDoesNotCreateNewOrderWhenFailedRefundExists(t *testing.T) {
	db := setupPaymentTestDB(t)
	repo := NewRepository(db, nil, nil)
	order := model.RentalOrder{
		ID:                1001,
		OrderNo:           "ORD202606140001",
		RenterID:          11,
		RentAmountCent:    800,
		DepositAmountCent: 200,
		RefundStatus:      "failed",
		RefundAmountCent:  1000,
		Status:            "closed",
	}
	if err := db.Create(&order).Error; err != nil {
		t.Fatalf("create order failed: %v", err)
	}
	original := model.PaymentOrder{
		PaymentNo:       "PAY202606140001",
		OrderID:         order.ID,
		OrderNo:         order.OrderNo,
		UserID:          order.RenterID,
		Provider:        "mock",
		ThirdOrderID:    "PAY202606140001",
		ProviderOrderID: "MOCKPAY202606140001",
		AmountCent:      1000,
		BizType:         "order_pay",
		Status:          "paid",
	}
	if err := db.Create(&original).Error; err != nil {
		t.Fatalf("create original payment failed: %v", err)
	}
	existingRefund := model.PaymentOrder{
		PaymentNo:    "PAY202606140002",
		OrderID:      order.ID,
		OrderNo:      order.OrderNo,
		UserID:       order.RenterID,
		Provider:     "mock",
		ThirdOrderID: "REF202606140002",
		AmountCent:   1000,
		BizType:      "admin_refund",
		Status:       "failed",
	}
	if err := db.Create(&existingRefund).Error; err != nil {
		t.Fatalf("create existing refund failed: %v", err)
	}

	dto, err := repo.StartRefund(t.Context(), order.ID, 1000, "admin_refund", "后台人工退款")
	if err != nil {
		t.Fatalf("StartRefund() error = %v", err)
	}
	if dto.ID != existingRefund.ID {
		t.Fatalf("StartRefund() returned payment id %d, want existing %d", dto.ID, existingRefund.ID)
	}
	var count int64
	if err := db.Model(&model.PaymentOrder{}).
		Where("order_id = ? AND biz_type = ?", order.ID, "admin_refund").
		Count(&count).Error; err != nil {
		t.Fatalf("count refund orders failed: %v", err)
	}
	if count != 1 {
		t.Fatalf("refund order count = %d, want 1", count)
	}
}

func TestConfirmPaidUpdatesOrderAndPaymentInOnePath(t *testing.T) {
	db := setupPaymentTestDB(t)
	orderRepo := ordermodule.NewRepository(db)
	repo := NewRepository(db, nil, orderRepo)
	fixture := createPayableOrderFixture(t, db, "0010", true)

	paidAt := time.Date(2026, 6, 14, 12, 0, 0, 0, time.UTC)
	err := repo.confirmPaid(t.Context(), &fixture.Payment, "paid", paidAt, map[string]string{
		"provider_order_id": "PROVIDER202606140010",
	}, channelSourceNotify)
	if err != nil {
		t.Fatalf("confirmPaid() error = %v", err)
	}

	var latestPayment model.PaymentOrder
	if err := db.First(&latestPayment, fixture.Payment.ID).Error; err != nil {
		t.Fatalf("find payment failed: %v", err)
	}
	if latestPayment.Status != "paid" {
		t.Fatalf("payment status = %q, want paid", latestPayment.Status)
	}
	if latestPayment.ProviderOrderID != "PROVIDER202606140010" {
		t.Fatalf("provider order id = %q, want provider response", latestPayment.ProviderOrderID)
	}

	var latestOrder model.RentalOrder
	if err := db.First(&latestOrder, fixture.Order.ID).Error; err != nil {
		t.Fatalf("find order failed: %v", err)
	}
	if latestOrder.Status != "pending_handoff" || latestOrder.HandoffStatus != "pending_owner" {
		t.Fatalf("order status = %q/%q, want pending_handoff/pending_owner", latestOrder.Status, latestOrder.HandoffStatus)
	}

	var latestListing model.RentalListing
	if err := db.First(&latestListing, fixture.Listing.ID).Error; err != nil {
		t.Fatalf("find listing failed: %v", err)
	}
	var latestAccount model.GameAccount
	if err := db.First(&latestAccount, fixture.Account.ID).Error; err != nil {
		t.Fatalf("find account failed: %v", err)
	}
	if latestListing.Status != "rented" || latestAccount.Status != "rented" {
		t.Fatalf("asset status = %q/%q, want rented/rented", latestListing.Status, latestAccount.Status)
	}
}

func TestConfirmPaidRollsBackOrderWhenPaymentUpdateFails(t *testing.T) {
	db := setupPaymentTestDB(t)
	orderRepo := ordermodule.NewRepository(db)
	repo := NewRepository(db, nil, orderRepo)
	fixture := createPayableOrderFixture(t, db, "0011", false)
	fixture.Payment.ID = 999999

	paidAt := time.Date(2026, 6, 14, 12, 0, 0, 0, time.UTC)
	err := repo.confirmPaid(t.Context(), &fixture.Payment, "paid", paidAt, map[string]string{
		"provider_order_id": "PROVIDER202606140011",
	}, channelSourceNotify)
	if err == nil {
		t.Fatal("confirmPaid() error = nil, want payment lookup failure")
	}

	var latestOrder model.RentalOrder
	if findErr := db.First(&latestOrder, fixture.Order.ID).Error; findErr != nil {
		t.Fatalf("find order failed: %v", findErr)
	}
	if latestOrder.Status != "pending_payment" || latestOrder.HandoffStatus != "none" {
		t.Fatalf("order status = %q/%q, want rollback to pending_payment/none", latestOrder.Status, latestOrder.HandoffStatus)
	}

	var latestListing model.RentalListing
	if findErr := db.First(&latestListing, fixture.Listing.ID).Error; findErr != nil {
		t.Fatalf("find listing failed: %v", findErr)
	}
	var latestAccount model.GameAccount
	if findErr := db.First(&latestAccount, fixture.Account.ID).Error; findErr != nil {
		t.Fatalf("find account failed: %v", findErr)
	}
	if latestListing.Status != "published" || latestAccount.Status != "published" {
		t.Fatalf("asset status = %q/%q, want rollback to published/published", latestListing.Status, latestAccount.Status)
	}
}

// TestQueryClosedPaymentReturnsLocalStatus 验证关闭状态不再依赖渠道配置或继续轮询渠道。
func TestQueryClosedPaymentReturnsLocalStatus(t *testing.T) {
	db := setupPaymentTestDB(t)
	repo := NewRepository(db, nil, nil)
	payment := model.PaymentOrder{
		PaymentNo:    "PAY20260614CLOSED",
		OrderID:      9001,
		OrderNo:      "ORD20260614CLOSED",
		UserID:       8001,
		Provider:     "lakala",
		ThirdOrderID: "PAY20260614CLOSED",
		AmountCent:   1000,
		BizType:      "order_pay",
		Status:       "closed",
	}
	if err := db.Create(&payment).Error; err != nil {
		t.Fatalf("create payment failed: %v", err)
	}

	dto, err := repo.Query(t.Context(), payment.UserID, payment.OrderID)
	if err != nil {
		t.Fatalf("Query() error = %v", err)
	}
	if dto.Status != "closed" {
		t.Fatalf("status = %q, want closed", dto.Status)
	}
}

// TestApplyChannelStatusKeepsTerminalPayment 验证已关闭支付单不会被渠道普通状态重新刷回支付中。
func TestApplyChannelStatusKeepsTerminalPayment(t *testing.T) {
	db := setupPaymentTestDB(t)
	repo := NewRepository(db, nil, nil)
	payment := model.PaymentOrder{
		PaymentNo:    "PAY20260614TERMINAL",
		OrderID:      9002,
		OrderNo:      "ORD20260614TERMINAL",
		UserID:       8002,
		Provider:     "lakala",
		ThirdOrderID: "PAY20260614TERMINAL",
		AmountCent:   1000,
		BizType:      "order_pay",
		Status:       "closed",
	}
	if err := db.Create(&payment).Error; err != nil {
		t.Fatalf("create payment failed: %v", err)
	}

	if err := repo.applyChannelStatus(t.Context(), &payment, "paying", "", map[string]string{"status": "paying"}, channelSourceNotify); err != nil {
		t.Fatalf("applyChannelStatus() error = %v", err)
	}

	var latest model.PaymentOrder
	if err := db.First(&latest, payment.ID).Error; err != nil {
		t.Fatalf("find payment failed: %v", err)
	}
	if latest.Status != "closed" {
		t.Fatalf("status = %q, want closed", latest.Status)
	}
}
