package services

import (
	"database/sql"
	"fmt"
	"testing"

	"quyun/v2/app/commands/testx"
	"quyun/v2/database"
	"quyun/v2/database/models"
	"quyun/v2/pkg/consts"
	"quyun/v2/pkg/utils"

	"github.com/samber/lo"
	. "github.com/smartystreets/goconvey/convey"
	"github.com/stretchr/testify/suite"

	_ "go.ipao.vip/atom"
	"go.ipao.vip/atom/contracts"
	"go.ipao.vip/gen/types"
	"go.uber.org/dig"
)

type UserTestSuiteInjectParams struct {
	dig.In

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

type UserTestSuite struct {
	suite.Suite

	UserTestSuiteInjectParams
}

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

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

func (t *UserTestSuite) Test_Create() {
	Convey("test user create", t.T(), func() {
		database.Truncate(t.T().Context(), t.DB, models.TableNameUser)

		m := &models.User{
			Username: "test-user",
			Password: "test-password",
			Roles:    types.NewArray([]consts.Role{consts.RoleUser}),
			Status:   consts.UserStatusPendingVerify,
		}

		err := m.Create(t.T().Context())
		So(err, ShouldBeNil)

		So(m.ID, ShouldBeGreaterThan, 0)

		same := m.ComparePassword(t.T().Context(), "test-password")
		So(same, ShouldBeTrue)

		same = m.ComparePassword(t.T().Context(), "test-password1")
		So(same, ShouldBeFalse)
	})
}

// FindByUsername
func (t *UserTestSuite) Test_FindByUsername() {
	Convey("test user FindByUsername", t.T(), func() {
		database.Truncate(t.T().Context(), t.DB, models.TableNameUser)

		Convey("user table is empty", func() {
			m, err := User.FindByUsername(t.T().Context(), "test-user")
			So(err, ShouldNotBeNil)
			So(m, ShouldBeNil)
		})

		Convey("insert one record", func() {
			username := "test-user"
			m := &models.User{
				Username: username,
				Password: "test-password",
				Roles:    types.NewArray([]consts.Role{consts.RoleUser}),
				Status:   consts.UserStatusPendingVerify,
			}

			err := m.Create(t.T().Context())
			So(err, ShouldBeNil)

			Convey("user table is not empty", func() {
				m, err := User.FindByUsername(t.T().Context(), username)
				So(err, ShouldBeNil)
				So(m, ShouldNotBeNil)
			})
		})
	})
}

// Test_Page
func (t *UserTestSuite) Test_Page() {
	FocusConvey("test page", t.T(), func() {
		Convey("filter username", func() {
			database.Truncate(t.T().Context(), t.DB, models.TableNameUser)

			username := "test-user"
			m := &models.User{
				Username: username,
				Password: "test-password",
				Roles:    types.NewArray([]consts.Role{consts.RoleUser}),
				Status:   consts.UserStatusPendingVerify,
			}

			err := m.Create(t.T().Context())
			So(err, ShouldBeNil)

			pager, err := User.Page(t.T().Context(), &UserPageFilter{
				Username: &username,
			})

			So(err, ShouldBeNil)
			So(pager.Total, ShouldEqual, 1)
		})

		FocusConvey("filter tenant users", func() {
			database.Truncate(
				t.T().Context(),
				t.DB,
				models.TableNameUser,
				models.TableNameTenant,
				models.TableNameTenantUser,
			)

			username := "test-user"
			m := &models.User{
				Username: username,
				Password: "test-password",
				Roles:    types.NewArray([]consts.Role{consts.RoleUser}),
				Status:   consts.UserStatusPendingVerify,
			}

			err := m.Create(t.T().Context())
			So(err, ShouldBeNil)

			m = &models.User{
				Username: username + "02",
				Password: "test-password",
				Roles:    types.NewArray([]consts.Role{consts.RoleUser}),
				Status:   consts.UserStatusPendingVerify,
			}

			err = m.Create(t.T().Context())
			So(err, ShouldBeNil)

			tenantModel := &models.Tenant{
				UserID: 1,
				Code:   "abc",
				UUID:   types.NewUUIDv4(),
				Name:   "T01",
				Status: consts.TenantStatusVerified,
			}

			err = tenantModel.Create(t.T().Context())
			So(err, ShouldBeNil)

			tenantModel = &models.Tenant{
				UserID: 2,
				Code:   "abc01",
				UUID:   types.NewUUIDv4(),
				Name:   "T02",
				Status: consts.TenantStatusVerified,
			}

			err = tenantModel.Create(t.T().Context())
			So(err, ShouldBeNil)

			count := 12
			for i := 0; i < count; i++ {
				m = &models.User{
					Username: fmt.Sprintf("user_%d", i),
					Password: "test-password",
					Roles:    types.NewArray([]consts.Role{consts.RoleUser}),
					Status:   consts.UserStatusPendingVerify,
				}

				err = m.Create(t.T().Context())
				So(err, ShouldBeNil)

				// create tenant user
				err = Tenant.AddUser(t.T().Context(), int64(i%2+1), m.ID)
				So(err, ShouldBeNil)
			}

			pager, err := User.Page(t.T().Context(), &UserPageFilter{
				TenantID: lo.ToPtr(int64(1)),
			})

			So(err, ShouldBeNil)
			So(pager.Total, ShouldEqual, 6)
		})
	})
}

func (t *UserTestSuite) Test_Relations() {
	Convey("test page", t.T(), func() {
		// database.Truncate(
		// 	t.T().Context(),
		// 	t.DB,
		// 	models.TableNameUser,
		// 	models.TableNameTenant,
		// 	models.TableNameTenantUser,
		// )

		// username := "test-user"
		// mUser01 := &models.User{
		// 	Username: username,
		// 	Password: "test-password",
		// 	Roles:    types.NewArray([]consts.Role{consts.RoleUser}),
		// 	Status:   consts.UserStatusPendingVerify,
		// }

		// err := mUser01.Create(t.T().Context())
		// So(err, ShouldBeNil)

		// tenantModel := &models.Tenant{
		// 	UserID: 1,
		// 	Code:   "abc",
		// 	UUID:   types.NewUUIDv4(),
		// 	Name:   "T01",
		// 	Status: consts.TenantStatusVerified,
		// }

		// err = tenantModel.Create(t.T().Context())
		// So(err, ShouldBeNil)

		// count := 10
		// for i := 0; i < count; i++ {
		// 	mUser := &models.User{
		// 		Username: fmt.Sprintf("user_%d", i),
		// 		Password: "test-password",
		// 		Roles:    types.NewArray([]consts.Role{consts.RoleUser}),
		// 		Status:   consts.UserStatusPendingVerify,
		// 	}

		// 	err = mUser.Create(t.T().Context())
		// 	So(err, ShouldBeNil)

		// 	// create tenant user
		// 	err = Tenant.AddUser(t.T().Context(), 1, mUser.ID)
		// 	So(err, ShouldBeNil)
		// }

		Convey("filter tenant users", func() {
			m, err := User.FindByID(t.T().Context(), 1)
			So(err, ShouldBeNil)
			// So(m.OwnedTenant, ShouldNotBeNil)
			// So(m.Tenants, ShouldHaveLength, 10)
			t.T().Logf("%s", utils.MustJsonString(m))
		})
	})
}
