package services

import (
	"database/sql"
	"testing"

	"quyun/v2/app/commands/testx"
	order_dto "quyun/v2/app/http/v1/dto"
	"quyun/v2/database"
	"quyun/v2/database/models"
	"quyun/v2/pkg/consts"

	. "github.com/smartystreets/goconvey/convey"
	"github.com/stretchr/testify/suite"
	"go.ipao.vip/atom/contracts"
	"go.uber.org/dig"
)

type OrderTestSuiteInjectParams struct {
	dig.In

	DB       *sql.DB
	Initials []contracts.Initial `group:"initials"`
}

type OrderTestSuite struct {
	suite.Suite
	OrderTestSuiteInjectParams
}

func Test_Order(t *testing.T) {
	providers := testx.Default().With(Provide)

	testx.Serve(providers, t, func(p OrderTestSuiteInjectParams) {
		suite.Run(t, &OrderTestSuite{OrderTestSuiteInjectParams: p})
	})
}

func (s *OrderTestSuite) Test_PurchaseFlow() {
	Convey("Purchase Flow", s.T(), func() {
		ctx := s.T().Context()
		tenantID := int64(0)
		database.Truncate(ctx, s.DB,
			models.TableNameOrder, models.TableNameOrderItem, models.TableNameUser,
			models.TableNameContent, models.TableNameContentPrice, models.TableNameTenant,
			models.TableNameContentAccess, models.TableNameTenantLedger,
		)

		// 1. Setup Data
		// Creator
		creator := &models.User{Username: "creator", Phone: "13800000001"}
		models.UserQuery.WithContext(ctx).Create(creator)
		// Tenant
		tenant := &models.Tenant{
			UserID: creator.ID,
			Name:   "Music Shop",
			Code:   "shop1",
			Status: consts.TenantStatusVerified,
		}
		models.TenantQuery.WithContext(ctx).Create(tenant)
		tenantID = tenant.ID
		// Content
		content := &models.Content{
			TenantID: tenant.ID,
			UserID:   creator.ID,
			Title:    "Song A",
			Status:   consts.ContentStatusPublished,
		}
		models.ContentQuery.WithContext(ctx).Create(content)
		// Price (10.00 CNY = 1000 cents)
		price := &models.ContentPrice{
			TenantID:    tenant.ID,
			ContentID:   content.ID,
			PriceAmount: 1000,
			Currency:    consts.CurrencyCNY,
		}
		models.ContentPriceQuery.WithContext(ctx).Create(price)

		// Buyer
		buyer := &models.User{Username: "buyer", Phone: "13900000001", Balance: 2000} // Has 20.00
		models.UserQuery.WithContext(ctx).Create(buyer)

		// buyerCtx := context.WithValue(ctx, consts.CtxKeyUser, buyer.ID)

		Convey("should create and pay order successfully", func() {
			// Step 1: Create Order
			form := &order_dto.OrderCreateForm{ContentID: content.ID}
			createRes, err := Order.Create(ctx, tenantID, buyer.ID, form)
			So(err, ShouldBeNil)
			So(createRes.OrderID, ShouldNotBeEmpty)

			// Verify created status
			oid := createRes.OrderID
			o, _ := models.OrderQuery.WithContext(ctx).Where(models.OrderQuery.ID.Eq(oid)).First()
			So(o.Status, ShouldEqual, consts.OrderStatusCreated)
			So(o.AmountPaid, ShouldEqual, 1000)

			// Step 2: Pay Order
			payForm := &order_dto.OrderPayForm{Method: "balance"}
			_, err = Order.Pay(ctx, tenantID, buyer.ID, createRes.OrderID, payForm)
			So(err, ShouldBeNil)

			// Verify Order Paid
			o, _ = models.OrderQuery.WithContext(ctx).Where(models.OrderQuery.ID.Eq(oid)).First()
			So(o.Status, ShouldEqual, consts.OrderStatusPaid)
			So(o.PaidAt, ShouldNotBeZeroValue)

			// Verify Balance Deducted
			b, _ := models.UserQuery.WithContext(ctx).Where(models.UserQuery.ID.Eq(buyer.ID)).First()
			So(b.Balance, ShouldEqual, 1000) // 2000 - 1000

			// Verify Access Granted
			access, _ := models.ContentAccessQuery.WithContext(ctx).
				Where(models.ContentAccessQuery.UserID.Eq(buyer.ID), models.ContentAccessQuery.ContentID.Eq(content.ID)).
				First()
			So(access, ShouldNotBeNil)
			So(access.Status, ShouldEqual, consts.ContentAccessStatusActive)

			// Verify Ledger Created (Creator received money logic?)
			// Note: My implementation credits the TENANT OWNER (creator.ID).
			l, _ := models.TenantLedgerQuery.WithContext(ctx).Where(models.TenantLedgerQuery.OrderID.Eq(o.ID)).First()
			So(l, ShouldNotBeNil)
			So(l.UserID, ShouldEqual, creator.ID)
			So(l.Amount, ShouldEqual, 900)
			So(l.Type, ShouldEqual, consts.TenantLedgerTypeDebitPurchase)
		})

		Convey("should fail pay if insufficient balance", func() {
			// Set balance to 5.00
			models.UserQuery.WithContext(ctx).
				Where(models.UserQuery.ID.Eq(buyer.ID)).
				Update(models.UserQuery.Balance, 500)

			form := &order_dto.OrderCreateForm{ContentID: content.ID}
			createRes, err := Order.Create(ctx, tenantID, buyer.ID, form)
			So(err, ShouldBeNil)

			payForm := &order_dto.OrderPayForm{Method: "balance"}
			_, err = Order.Pay(ctx, tenantID, buyer.ID, createRes.OrderID, payForm)
			So(err, ShouldNotBeNil)
			// Error should be QuotaExceeded or similar
		})
	})
}

func (s *OrderTestSuite) Test_OrderDetails() {
	Convey("Order Details", s.T(), func() {
		ctx := s.T().Context()
		tenantID := int64(0)
		database.Truncate(
			ctx,
			s.DB,
			models.TableNameOrder,
			models.TableNameOrderItem,
			models.TableNameUser,
			models.TableNameContent,
			models.TableNameContentPrice,
			models.TableNameTenant,
			models.TableNameContentAccess,
			models.TableNameTenantLedger,
			models.TableNameMediaAsset,
			models.TableNameContentAsset,
		)

		// Setup
		creator := &models.User{Username: "creator2", Phone: "13800000002"}
		models.UserQuery.WithContext(ctx).Create(creator)
		tenant := &models.Tenant{UserID: creator.ID, Name: "Best Shop", Status: consts.TenantStatusVerified}
		models.TenantQuery.WithContext(ctx).Create(tenant)
		tenantID = tenant.ID
		content := &models.Content{
			TenantID: tenant.ID,
			UserID:   creator.ID,
			Title:    "Amazing Song",
			Status:   consts.ContentStatusPublished,
		}
		models.ContentQuery.WithContext(ctx).Create(content)
		price := &models.ContentPrice{
			TenantID:    tenant.ID,
			ContentID:   content.ID,
			PriceAmount: 500,
			Currency:    consts.CurrencyCNY,
		}
		models.ContentPriceQuery.WithContext(ctx).Create(price)

		// Asset (Cover)
		asset := &models.MediaAsset{
			TenantID:  tenant.ID,
			UserID:    creator.ID,
			Type:      consts.MediaAssetTypeImage,
			ObjectKey: "cover.jpg",
		}
		models.MediaAssetQuery.WithContext(ctx).Create(asset)
		models.ContentAssetQuery.WithContext(ctx).
			Create(&models.ContentAsset{ContentID: content.ID, AssetID: asset.ID, Role: consts.ContentAssetRoleCover})

		// Buyer
		buyer := &models.User{Username: "buyer2", Phone: "13900000002", Balance: 1000}
		models.UserQuery.WithContext(ctx).Create(buyer)
		// buyerCtx := context.WithValue(ctx, consts.CtxKeyUser, buyer.ID)

		Convey("should get full order details", func() {
			// Create & Pay
			createRes, _ := Order.Create(
				ctx,
				tenantID,
				buyer.ID,
				&order_dto.OrderCreateForm{ContentID: content.ID},
			)
			Order.Pay(ctx, tenantID, buyer.ID, createRes.OrderID, &order_dto.OrderPayForm{Method: "balance"})

			// Get Detail
			detail, err := Order.GetUserOrder(ctx, tenantID, buyer.ID, createRes.OrderID)
			So(err, ShouldBeNil)
			So(detail.TenantName, ShouldEqual, "Best Shop")
			So(len(detail.Items), ShouldEqual, 1)
			So(detail.Items[0].Title, ShouldEqual, "Amazing Song")
			So(detail.Items[0].Cover, ShouldContainSubstring, "cover.jpg")
			So(detail.Amount, ShouldEqual, 5.00)
		})
	})
}

func (s *OrderTestSuite) Test_PlatformCommission() {
	Convey("Platform Commission", s.T(), func() {
		ctx := s.T().Context()
		tenantID := int64(0)
		database.Truncate(
			ctx,
			s.DB,
			models.TableNameUser,
			models.TableNameOrder,
			models.TableNameOrderItem,
			models.TableNameTenant,
			models.TableNameTenantLedger,
			models.TableNameContentAccess,
		)

		// Creator
		creator := &models.User{Username: "creator_c", Balance: 0}
		models.UserQuery.WithContext(ctx).Create(creator)
		// Tenant
		t := &models.Tenant{UserID: creator.ID, Name: "Shop C", Status: consts.TenantStatusVerified}
		models.TenantQuery.WithContext(ctx).Create(t)
		tenantID = t.ID
		// Buyer
		buyer := &models.User{Username: "buyer_c", Balance: 2000}
		models.UserQuery.WithContext(ctx).Create(buyer)
		// buyerCtx := context.WithValue(ctx, consts.CtxKeyUser, buyer.ID)

		// Order (10.00 CNY = 1000)
		o := &models.Order{
			TenantID:   t.ID,
			UserID:     buyer.ID,
			AmountPaid: 1000,
			Status:     consts.OrderStatusCreated,
		}
		models.OrderQuery.WithContext(ctx).Create(o)
		models.OrderItemQuery.WithContext(ctx).Create(&models.OrderItem{OrderID: o.ID, ContentID: 999}) // Fake content

		Convey("should deduct 10% fee", func() {
			payForm := &order_dto.OrderPayForm{Method: "balance"}
			_, err := Order.Pay(ctx, tenantID, buyer.ID, o.ID, payForm)
			So(err, ShouldBeNil)

			// Verify Creator Balance (1000 - 10% = 900)
			cReload, _ := models.UserQuery.WithContext(ctx).Where(models.UserQuery.ID.Eq(creator.ID)).First()
			So(cReload.Balance, ShouldEqual, 900)

			// Verify Ledger
			l, _ := models.TenantLedgerQuery.WithContext(ctx).Where(models.TenantLedgerQuery.OrderID.Eq(o.ID)).First()
			So(l.Amount, ShouldEqual, 900)
		})
	})
}

func (s *OrderTestSuite) Test_ExternalPayment() {
	Convey("External Payment", s.T(), func() {
		ctx := s.T().Context()
		tenantID := int64(0)
		database.Truncate(
			ctx,
			s.DB,
			models.TableNameUser,
			models.TableNameOrder,
			models.TableNameOrderItem,
			models.TableNameTenant,
			models.TableNameTenantLedger,
			models.TableNameContentAccess,
		)

		// Creator
		creator := &models.User{Username: "creator_ext", Balance: 0}
		models.UserQuery.WithContext(ctx).Create(creator)
		// Tenant
		t := &models.Tenant{UserID: creator.ID, Name: "Shop Ext", Status: consts.TenantStatusVerified}
		models.TenantQuery.WithContext(ctx).Create(t)
		tenantID = t.ID
		// Buyer (Balance 0)
		buyer := &models.User{Username: "buyer_ext", Balance: 0}
		models.UserQuery.WithContext(ctx).Create(buyer)

		// Order
		o := &models.Order{
			TenantID:   t.ID,
			UserID:     buyer.ID,
			AmountPaid: 1000,
			Status:     consts.OrderStatusCreated,
		}
		models.OrderQuery.WithContext(ctx).Create(o)
		models.OrderItemQuery.WithContext(ctx).Create(&models.OrderItem{OrderID: o.ID, ContentID: 999})

		Convey("should process external payment callback", func() {
			err := Order.ProcessExternalPayment(ctx, tenantID, o.ID, "ext_tx_id_123")
			So(err, ShouldBeNil)

			// Verify Status
			oReload, _ := models.OrderQuery.WithContext(ctx).Where(models.OrderQuery.ID.Eq(o.ID)).First()
			So(oReload.Status, ShouldEqual, consts.OrderStatusPaid)

			// Verify Creator Credited
			cReload, _ := models.UserQuery.WithContext(ctx).Where(models.UserQuery.ID.Eq(creator.ID)).First()
			So(cReload.Balance, ShouldEqual, 900) // 1000 - 10%

			// Verify Buyer Balance (Should NOT be deducted)
			bReload, _ := models.UserQuery.WithContext(ctx).Where(models.UserQuery.ID.Eq(buyer.ID)).First()
			So(bReload.Balance, ShouldEqual, 0)
		})
	})
}
