package services

import (
	"database/sql"
	"testing"
	"time"

	"quyun/v2/app/commands/testx"
	super_dto "quyun/v2/app/http/super/v1/dto"
	"quyun/v2/app/requests"
	"quyun/v2/database"
	"quyun/v2/database/models"
	"quyun/v2/pkg/consts"

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

type SuperTestSuiteInjectParams struct {
	dig.In

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

type SuperTestSuite struct {
	suite.Suite
	SuperTestSuiteInjectParams
}

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

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

func (s *SuperTestSuite) Test_ListUsers() {
	Convey("ListUsers", s.T(), func() {
		ctx := s.T().Context()
		database.Truncate(ctx, s.DB, models.TableNameUser)

		u1 := &models.User{Username: "user1", Nickname: "Alice"}
		u2 := &models.User{Username: "user2", Nickname: "Bob"}
		models.UserQuery.WithContext(ctx).Create(u1, u2)

		Convey("should list users", func() {
			filter := &super_dto.UserListFilter{
				Pagination: requests.Pagination{Page: 1, Limit: 10},
			}
			res, err := Super.ListUsers(ctx, filter)
			So(err, ShouldBeNil)
			So(res.Total, ShouldEqual, 2)

			items := res.Items.([]super_dto.UserItem)
			So(items[0].Username, ShouldEqual, "user2") // Desc order
		})

		Convey("should filter users", func() {
			filter := &super_dto.UserListFilter{
				Pagination: requests.Pagination{Page: 1, Limit: 10},
				Username:   lo.ToPtr("Alice"),
			}
			res, err := Super.ListUsers(ctx, filter)
			So(err, ShouldBeNil)
			So(res.Total, ShouldEqual, 1)
			items := res.Items.([]super_dto.UserItem)
			So(items[0].Username, ShouldEqual, "user1")
		})
	})
}

func (s *SuperTestSuite) Test_LoginAndCheckToken() {
	Convey("Login and CheckToken", s.T(), func() {
		ctx := s.T().Context()
		database.Truncate(ctx, s.DB, models.TableNameUser)

		admin := &models.User{
			Username: "super_admin",
			Password: "pass123",
			Roles:    types.Array[consts.Role]{consts.RoleSuperAdmin},
			Status:   consts.UserStatusVerified,
		}
		normal := &models.User{
			Username: "normal_user",
			Password: "pass123",
			Status:   consts.UserStatusVerified,
		}
		models.UserQuery.WithContext(ctx).Create(admin, normal)

		Convey("should login as super admin", func() {
			res, err := Super.Login(ctx, &super_dto.LoginForm{
				Username: admin.Username,
				Password: admin.Password,
			})
			So(err, ShouldBeNil)
			So(res, ShouldNotBeNil)
			So(res.Token, ShouldNotBeBlank)
			So(res.User.ID, ShouldEqual, admin.ID)
		})

		Convey("should reject non-super admin", func() {
			_, err := Super.Login(ctx, &super_dto.LoginForm{
				Username: normal.Username,
				Password: normal.Password,
			})
			So(err, ShouldNotBeNil)
		})

		Convey("should refresh token", func() {
			loginRes, err := Super.Login(ctx, &super_dto.LoginForm{
				Username: admin.Username,
				Password: admin.Password,
			})
			So(err, ShouldBeNil)

			token := "Bearer " + loginRes.Token
			checkRes, err := Super.CheckToken(ctx, token)
			So(err, ShouldBeNil)
			So(checkRes, ShouldNotBeNil)
			So(checkRes.Token, ShouldNotBeBlank)
			So(checkRes.User.ID, ShouldEqual, admin.ID)
		})
	})
}

func (s *SuperTestSuite) Test_CreateTenant() {
	Convey("CreateTenant", s.T(), func() {
		ctx := s.T().Context()
		database.Truncate(ctx, s.DB, models.TableNameUser, models.TableNameTenant)

		u := &models.User{Username: "admin1"}
		models.UserQuery.WithContext(ctx).Create(u)

		Convey("should create tenant", func() {
			form := &super_dto.TenantCreateForm{
				Name:        "Super Tenant",
				Code:        "st1",
				AdminUserID: u.ID,
			}
			err := Super.CreateTenant(ctx, form)
			So(err, ShouldBeNil)

			t, _ := models.TenantQuery.WithContext(ctx).Where(models.TenantQuery.Code.Eq("st1")).First()
			So(t, ShouldNotBeNil)
			So(t.Name, ShouldEqual, "Super Tenant")
			So(t.UserID, ShouldEqual, u.ID)
			So(t.Status, ShouldEqual, consts.TenantStatusVerified)
		})
	})
}

func (s *SuperTestSuite) Test_WithdrawalApproval() {
	Convey("Withdrawal Approval", s.T(), func() {
		ctx := s.T().Context()
		database.Truncate(ctx, s.DB, models.TableNameOrder, models.TableNameUser, models.TableNameTenantLedger)

		u := &models.User{Username: "user_w", Balance: 1000} // Initial 10.00
		admin := &models.User{Username: "admin_w"}
		models.UserQuery.WithContext(ctx).Create(u, admin)

		// Create Withdrawal Order (Pending)
		o1 := &models.Order{
			UserID:     u.ID,
			Type:       consts.OrderTypeWithdrawal,
			Status:     consts.OrderStatusCreated,
			AmountPaid: 500,
		}
		models.OrderQuery.WithContext(ctx).Create(o1)

		Convey("should list withdrawals", func() {
			filter := &super_dto.SuperOrderListFilter{Pagination: requests.Pagination{Page: 1, Limit: 10}}
			res, err := Super.ListWithdrawals(ctx, filter)
			So(err, ShouldBeNil)
			So(res.Total, ShouldEqual, 1)
		})

		Convey("should approve withdrawal", func() {
			err := Super.ApproveWithdrawal(ctx, admin.ID, o1.ID)
			So(err, ShouldBeNil)

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

		Convey("should reject withdrawal and refund", func() {
			// Another order
			o2 := &models.Order{
				UserID:     u.ID,
				Type:       consts.OrderTypeWithdrawal,
				Status:     consts.OrderStatusCreated,
				AmountPaid: 200,
			}
			models.OrderQuery.WithContext(ctx).Create(o2)

			// Assuming user balance was deducted when o2 was created (logic in creator service)
			// But here we set balance manually to 1000. Let's assume it was 1200 before.
			// Current balance 1000. Refund 200 -> Expect 1200.

			err := Super.RejectWithdrawal(ctx, admin.ID, o2.ID, "Invalid account")
			So(err, ShouldBeNil)

			oReload, _ := models.OrderQuery.WithContext(ctx).Where(models.OrderQuery.ID.Eq(o2.ID)).First()
			So(oReload.Status, ShouldEqual, consts.OrderStatusFailed)

			uReload, _ := models.UserQuery.WithContext(ctx).Where(models.UserQuery.ID.Eq(u.ID)).First()
			So(uReload.Balance, ShouldEqual, 1200)

			// Check Ledger
			l, _ := models.TenantLedgerQuery.WithContext(ctx).Where(models.TenantLedgerQuery.OrderID.Eq(o2.ID)).First()
			So(l, ShouldNotBeNil)
			So(l.Type, ShouldEqual, consts.TenantLedgerTypeAdjustment)
			So(l.OperatorUserID, ShouldEqual, admin.ID)
		})
	})
}

func (s *SuperTestSuite) Test_TenantHealth() {
	Convey("TenantHealth", s.T(), func() {
		ctx := s.T().Context()
		database.Truncate(
			ctx,
			s.DB,
			models.TableNameUser,
			models.TableNameTenant,
			models.TableNameTenantUser,
			models.TableNameContent,
			models.TableNameOrder,
		)

		owner1 := &models.User{Username: "health_owner_1"}
		owner2 := &models.User{Username: "health_owner_2"}
		models.UserQuery.WithContext(ctx).Create(owner1, owner2)

		tenant1 := &models.Tenant{
			UserID: owner1.ID,
			Name:   "Health Tenant 1",
			Code:   "health1",
			Status: consts.TenantStatusVerified,
		}
		tenant2 := &models.Tenant{
			UserID: owner2.ID,
			Name:   "Health Tenant 2",
			Code:   "health2",
			Status: consts.TenantStatusVerified,
		}
		models.TenantQuery.WithContext(ctx).Create(tenant1, tenant2)

		models.TenantUserQuery.WithContext(ctx).Create(
			&models.TenantUser{TenantID: tenant1.ID, UserID: owner1.ID},
			&models.TenantUser{TenantID: tenant2.ID, UserID: owner2.ID},
		)

		models.ContentQuery.WithContext(ctx).Create(
			&models.Content{
				TenantID: tenant1.ID,
				UserID:   owner1.ID,
				Title:    "Content H1",
				Status:   consts.ContentStatusPublished,
			},
			&models.Content{
				TenantID: tenant2.ID,
				UserID:   owner2.ID,
				Title:    "Content H2",
				Status:   consts.ContentStatusPublished,
			},
		)

		now := time.Now()
		models.OrderQuery.WithContext(ctx).Create(
			&models.Order{
				TenantID:   tenant1.ID,
				UserID:     owner1.ID,
				Type:       consts.OrderTypeContentPurchase,
				Status:     consts.OrderStatusPaid,
				AmountPaid: 1000,
				PaidAt:     now,
			},
			&models.Order{
				TenantID:   tenant2.ID,
				UserID:     owner2.ID,
				Type:       consts.OrderTypeContentPurchase,
				Status:     consts.OrderStatusPaid,
				AmountPaid: 1000,
				PaidAt:     now,
			},
			&models.Order{
				TenantID:   tenant2.ID,
				UserID:     owner2.ID,
				Type:       consts.OrderTypeContentPurchase,
				Status:     consts.OrderStatusRefunded,
				AmountPaid: 1000,
				UpdatedAt:  now,
			},
		)

		filter := &super_dto.TenantListFilter{
			Pagination: requests.Pagination{Page: 1, Limit: 10},
		}
		res, err := Super.TenantHealth(ctx, filter)
		So(err, ShouldBeNil)
		So(res.Total, ShouldEqual, 2)

		items := res.Items.([]super_dto.TenantHealthItem)
		itemMap := make(map[int64]super_dto.TenantHealthItem, len(items))
		for _, item := range items {
			itemMap[item.TenantID] = item
		}

		So(itemMap[tenant1.ID].PaidOrders, ShouldEqual, 1)
		So(itemMap[tenant1.ID].RefundOrders, ShouldEqual, 0)
		So(itemMap[tenant1.ID].HealthLevel, ShouldEqual, "healthy")

		So(itemMap[tenant2.ID].PaidOrders, ShouldEqual, 1)
		So(itemMap[tenant2.ID].RefundOrders, ShouldEqual, 1)
		So(itemMap[tenant2.ID].HealthLevel, ShouldEqual, "risk")
	})
}

func (s *SuperTestSuite) Test_ContentReview() {
	Convey("ContentReview", s.T(), func() {
		ctx := s.T().Context()
		database.Truncate(ctx, s.DB, models.TableNameUser, models.TableNameTenant, models.TableNameContent)

		admin := &models.User{Username: "review_admin"}
		owner := &models.User{Username: "review_owner"}
		models.UserQuery.WithContext(ctx).Create(admin, owner)

		tenant := &models.Tenant{
			UserID: owner.ID,
			Name:   "Review Tenant",
			Code:   "review",
			Status: consts.TenantStatusVerified,
		}
		models.TenantQuery.WithContext(ctx).Create(tenant)

		content := &models.Content{
			TenantID: tenant.ID,
			UserID:   owner.ID,
			Title:    "Review Content",
			Status:   consts.ContentStatusReviewing,
		}
		models.ContentQuery.WithContext(ctx).Create(content)

		err := Super.ReviewContent(ctx, admin.ID, content.ID, &super_dto.SuperContentReviewForm{
			Action: "approve",
		})
		So(err, ShouldBeNil)

		reloaded, _ := models.ContentQuery.WithContext(ctx).Where(models.ContentQuery.ID.Eq(content.ID)).First()
		So(reloaded.Status, ShouldEqual, consts.ContentStatusPublished)
		So(reloaded.PublishedAt.IsZero(), ShouldBeFalse)

		content2 := &models.Content{
			TenantID: tenant.ID,
			UserID:   owner.ID,
			Title:    "Review Content 2",
			Status:   consts.ContentStatusReviewing,
		}
		models.ContentQuery.WithContext(ctx).Create(content2)

		err = Super.ReviewContent(ctx, admin.ID, content2.ID, &super_dto.SuperContentReviewForm{
			Action: "reject",
			Reason: "Policy violation",
		})
		So(err, ShouldBeNil)

		reloaded2, _ := models.ContentQuery.WithContext(ctx).Where(models.ContentQuery.ID.Eq(content2.ID)).First()
		So(reloaded2.Status, ShouldEqual, consts.ContentStatusBlocked)
	})
}
