package services

import (
	"errors"
	"time"

	"quyun/v2/app/errorx"
	tenant_dto "quyun/v2/app/http/v1/dto"
	"quyun/v2/database"
	"quyun/v2/database/models"
	"quyun/v2/pkg/consts"

	. "github.com/smartystreets/goconvey/convey"
	"go.ipao.vip/gen/types"
)

func (s *TenantTestSuite) Test_ApplyJoin() {
	Convey("ApplyJoin", s.T(), func() {
		ctx := s.T().Context()

		setup := func() (*models.Tenant, *models.User, *models.User) {
			database.Truncate(ctx, s.DB,
				models.TableNameTenantJoinRequest,
				models.TableNameTenantUser,
				models.TableNameTenant,
				models.TableNameUser,
			)

			owner := &models.User{Username: "owner_apply", Phone: "13900001001"}
			user := &models.User{Username: "user_apply", Phone: "13900001002"}
			_ = models.UserQuery.WithContext(ctx).Create(owner)
			_ = models.UserQuery.WithContext(ctx).Create(user)

			tenant := &models.Tenant{
				Name:   "Tenant Apply",
				UserID: owner.ID,
				Status: consts.TenantStatusVerified,
			}
			_ = models.TenantQuery.WithContext(ctx).Create(tenant)
			return tenant, owner, user
		}

		Convey("should create pending join request", func() {
			tenant, _, user := setup()

			err := Tenant.ApplyJoin(ctx, tenant.ID, user.ID, &tenant_dto.TenantJoinApplyForm{Reason: "想加入"})
			So(err, ShouldBeNil)

			req, err := models.TenantJoinRequestQuery.WithContext(ctx).
				Where(models.TenantJoinRequestQuery.TenantID.Eq(tenant.ID),
					models.TenantJoinRequestQuery.UserID.Eq(user.ID)).
				First()
			So(err, ShouldBeNil)
			So(req.Status, ShouldEqual, string(consts.TenantJoinRequestStatusPending))
		})

		Convey("should reject duplicate apply", func() {
			tenant, _, user := setup()
			_ = Tenant.ApplyJoin(ctx, tenant.ID, user.ID, &tenant_dto.TenantJoinApplyForm{})

			err := Tenant.ApplyJoin(ctx, tenant.ID, user.ID, &tenant_dto.TenantJoinApplyForm{})
			So(err, ShouldNotBeNil)

			var appErr *errorx.AppError
			So(errors.As(err, &appErr), ShouldBeTrue)
			So(appErr.Code, ShouldEqual, errorx.ErrBadRequest.Code)
		})

		Convey("should reject when already member", func() {
			tenant, _, user := setup()
			_ = models.TenantUserQuery.WithContext(ctx).Create(&models.TenantUser{
				TenantID: tenant.ID,
				UserID:   user.ID,
				Role:     types.Array[consts.TenantUserRole]{consts.TenantUserRoleMember},
				Status:   consts.UserStatusVerified,
			})

			err := Tenant.ApplyJoin(ctx, tenant.ID, user.ID, &tenant_dto.TenantJoinApplyForm{})
			So(err, ShouldNotBeNil)

			var appErr *errorx.AppError
			So(errors.As(err, &appErr), ShouldBeTrue)
			So(appErr.Code, ShouldEqual, errorx.ErrBadRequest.Code)
		})
	})
}

func (s *TenantTestSuite) Test_CancelJoin() {
	Convey("CancelJoin", s.T(), func() {
		ctx := s.T().Context()

		setup := func() (*models.Tenant, *models.User, *models.User, *models.TenantJoinRequest) {
			database.Truncate(ctx, s.DB,
				models.TableNameTenantJoinRequest,
				models.TableNameTenantUser,
				models.TableNameTenant,
				models.TableNameUser,
			)

			owner := &models.User{Username: "owner_cancel", Phone: "13900001003"}
			user := &models.User{Username: "user_cancel", Phone: "13900001004"}
			_ = models.UserQuery.WithContext(ctx).Create(owner)
			_ = models.UserQuery.WithContext(ctx).Create(user)

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

			req := &models.TenantJoinRequest{
				TenantID: tenant.ID,
				UserID:   user.ID,
				Status:   string(consts.TenantJoinRequestStatusPending),
				Reason:   "测试取消",
			}
			_ = models.TenantJoinRequestQuery.WithContext(ctx).Create(req)
			return tenant, owner, user, req
		}

		Convey("should cancel pending request", func() {
			tenant, _, user, _ := setup()

			err := Tenant.CancelJoin(ctx, tenant.ID, user.ID)
			So(err, ShouldBeNil)

			exists, err := models.TenantJoinRequestQuery.WithContext(ctx).
				Where(models.TenantJoinRequestQuery.TenantID.Eq(tenant.ID),
					models.TenantJoinRequestQuery.UserID.Eq(user.ID)).
				Exists()
			So(err, ShouldBeNil)
			So(exists, ShouldBeFalse)
		})

		Convey("should return not found when request missing", func() {
			tenant, _, user, _ := setup()
			_ = Tenant.CancelJoin(ctx, tenant.ID, user.ID)

			err := Tenant.CancelJoin(ctx, tenant.ID, user.ID)
			So(err, ShouldNotBeNil)

			var appErr *errorx.AppError
			So(errors.As(err, &appErr), ShouldBeTrue)
			So(appErr.Code, ShouldEqual, errorx.ErrRecordNotFound.Code)
		})
	})
}

func (s *TenantTestSuite) Test_ReviewJoin() {
	Convey("ReviewJoin", s.T(), func() {
		ctx := s.T().Context()

		setup := func() (*models.Tenant, *models.User, *models.User, *models.TenantJoinRequest) {
			database.Truncate(ctx, s.DB,
				models.TableNameTenantJoinRequest,
				models.TableNameTenantUser,
				models.TableNameTenant,
				models.TableNameUser,
			)

			owner := &models.User{Username: "owner_review", Phone: "13900001005"}
			user := &models.User{Username: "user_review", Phone: "13900001006"}
			_ = models.UserQuery.WithContext(ctx).Create(owner)
			_ = models.UserQuery.WithContext(ctx).Create(user)

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

			req := &models.TenantJoinRequest{
				TenantID: tenant.ID,
				UserID:   user.ID,
				Status:   string(consts.TenantJoinRequestStatusPending),
				Reason:   "测试审核",
			}
			_ = models.TenantJoinRequestQuery.WithContext(ctx).Create(req)
			return tenant, owner, user, req
		}

		Convey("should approve join request", func() {
			tenant, owner, user, req := setup()

			err := Tenant.ReviewJoin(ctx, tenant.ID, owner.ID, req.ID, &tenant_dto.TenantJoinReviewForm{
				Action: "approve",
				Reason: "通过",
			})
			So(err, ShouldBeNil)

			req, err = models.TenantJoinRequestQuery.WithContext(ctx).Where(models.TenantJoinRequestQuery.ID.Eq(req.ID)).First()
			So(err, ShouldBeNil)
			So(req.Status, ShouldEqual, string(consts.TenantJoinRequestStatusApproved))

			exists, err := models.TenantUserQuery.WithContext(ctx).
				Where(models.TenantUserQuery.TenantID.Eq(tenant.ID),
					models.TenantUserQuery.UserID.Eq(user.ID)).
				Exists()
			So(err, ShouldBeNil)
			So(exists, ShouldBeTrue)
		})

		Convey("should reject join request", func() {
			tenant, owner, _, req := setup()

			err := Tenant.ReviewJoin(ctx, tenant.ID, owner.ID, req.ID, &tenant_dto.TenantJoinReviewForm{
				Action: "reject",
				Reason: "不符合条件",
			})
			So(err, ShouldBeNil)

			req, err = models.TenantJoinRequestQuery.WithContext(ctx).Where(models.TenantJoinRequestQuery.ID.Eq(req.ID)).First()
			So(err, ShouldBeNil)
			So(req.Status, ShouldEqual, string(consts.TenantJoinRequestStatusRejected))
		})
	})
}

func (s *TenantTestSuite) Test_CreateInvite() {
	Convey("CreateInvite", s.T(), func() {
		ctx := s.T().Context()
		database.Truncate(ctx, s.DB,
			models.TableNameTenantInvite,
			models.TableNameTenantUser,
			models.TableNameTenant,
			models.TableNameUser,
		)

		owner := &models.User{Username: "owner_invite", Phone: "13900001007"}
		_ = models.UserQuery.WithContext(ctx).Create(owner)
		tenant := &models.Tenant{
			Name:   "Tenant Invite",
			UserID: owner.ID,
			Status: consts.TenantStatusVerified,
		}
		_ = models.TenantQuery.WithContext(ctx).Create(tenant)

		expiresAt := time.Now().Add(24 * time.Hour).Format(time.RFC3339)
		item, err := Tenant.CreateInvite(ctx, tenant.ID, owner.ID, &tenant_dto.TenantInviteCreateForm{
			MaxUses:   2,
			ExpiresAt: &expiresAt,
			Remark:    "测试邀请",
		})
		So(err, ShouldBeNil)
		So(item.Code, ShouldNotBeBlank)

		invite, err := models.TenantInviteQuery.WithContext(ctx).Where(models.TenantInviteQuery.ID.Eq(item.ID)).First()
		So(err, ShouldBeNil)
		So(invite.MaxUses, ShouldEqual, 2)
		So(invite.Status, ShouldEqual, string(consts.TenantInviteStatusActive))
	})
}

func (s *TenantTestSuite) Test_AcceptInvite() {
	Convey("AcceptInvite", s.T(), func() {
		ctx := s.T().Context()
		database.Truncate(ctx, s.DB,
			models.TableNameTenantInvite,
			models.TableNameTenantUser,
			models.TableNameTenant,
			models.TableNameUser,
		)

		owner := &models.User{Username: "owner_accept", Phone: "13900001008"}
		user := &models.User{Username: "user_accept", Phone: "13900001009"}
		_ = models.UserQuery.WithContext(ctx).Create(owner)
		_ = models.UserQuery.WithContext(ctx).Create(user)

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

		invite := &models.TenantInvite{
			TenantID:  tenant.ID,
			UserID:    owner.ID,
			Code:      "invite_accept",
			Status:    string(consts.TenantInviteStatusActive),
			MaxUses:   1,
			UsedCount: 0,
			ExpiresAt: time.Now().Add(24 * time.Hour),
			Remark:    "测试使用",
		}
		_ = models.TenantInviteQuery.WithContext(ctx).Create(invite)

		err := Tenant.AcceptInvite(ctx, tenant.ID, user.ID, &tenant_dto.TenantInviteAcceptForm{Code: "invite_accept"})
		So(err, ShouldBeNil)

		exists, err := models.TenantUserQuery.WithContext(ctx).
			Where(models.TenantUserQuery.TenantID.Eq(tenant.ID),
				models.TenantUserQuery.UserID.Eq(user.ID)).
			Exists()
		So(err, ShouldBeNil)
		So(exists, ShouldBeTrue)

		invite, err = models.TenantInviteQuery.WithContext(ctx).Where(models.TenantInviteQuery.ID.Eq(invite.ID)).First()
		So(err, ShouldBeNil)
		So(invite.UsedCount, ShouldEqual, 1)
	})
}
