package services

import (
	"context"
	"database/sql"
	"errors"
	"testing"

	"quyun/v2/app/commands/testx"
	"quyun/v2/app/errorx"
	user_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.ipao.vip/gen/types"
	"go.uber.org/dig"
)

type UserTestSuiteInjectParams struct {
	dig.In

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

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 (s *UserTestSuite) Test_LoginWithOTP() {
	Convey("LoginWithOTP", s.T(), func() {
		ctx := s.T().Context()
		database.Truncate(ctx, s.DB, models.TableNameTenantUser, models.TableNameTenant, models.TableNameUser)

		tenant := &models.Tenant{
			UserID: 1000,
			Name:   "Tenant A",
			Code:   "tenant_a",
			Status: consts.TenantStatusVerified,
		}
		models.TenantQuery.WithContext(ctx).Create(tenant)

		Convey("should create user and login success without tenant", func() {
			phone := "13800138000"
			resp, err := User.LoginWithOTP(ctx, 0, phone, "1234")
			So(err, ShouldBeNil)
			So(resp, ShouldNotBeNil)
			So(resp.Token, ShouldNotBeEmpty)
			So(resp.User.Phone, ShouldEqual, phone)
			So(resp.User.Nickname, ShouldStartWith, "User_")
		})

		Convey("should reject login when not tenant member", func() {
			phone := "13800138001"
			_, err := User.LoginWithOTP(ctx, tenant.ID, phone, "1234")
			So(err, ShouldNotBeNil)

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

		Convey("should login existing tenant member", func() {
			phone := "13800138002"
			resp, err := User.LoginWithOTP(ctx, 0, phone, "1234")
			So(err, ShouldBeNil)

			models.TenantUserQuery.WithContext(ctx).Create(&models.TenantUser{
				TenantID: tenant.ID,
				UserID:   resp.User.ID,
				Role:     types.Array[consts.TenantUserRole]{consts.TenantUserRoleMember},
				Status:   consts.UserStatusVerified,
			})

			resp2, err := User.LoginWithOTP(ctx, tenant.ID, phone, "1234")
			So(err, ShouldBeNil)
			So(resp2.User.Phone, ShouldEqual, phone)
		})

		Convey("should fail with incorrect OTP", func() {
			resp, err := User.LoginWithOTP(ctx, 0, "13800138003", "000000")
			So(err, ShouldNotBeNil)
			So(resp, ShouldBeNil)
		})
	})
}

func (s *UserTestSuite) Test_Me() {
	Convey("Me", s.T(), func() {
		ctx := s.T().Context()
		tenantID := int64(0)
		database.Truncate(ctx, s.DB, models.TableNameUser)

		// Create user
		phone := "13800138003"
		resp, _ := User.LoginWithOTP(ctx, tenantID, phone, "1234")
		userID := resp.User.ID

		Convey("should return user profile", func() {
			// Mock context with user ID
			ctx = context.WithValue(ctx, consts.CtxKeyUser, userID)

			user, err := User.Me(ctx, userID)
			So(err, ShouldBeNil)
			So(user.ID, ShouldEqual, resp.User.ID)
			So(user.Phone, ShouldEqual, phone)
		})

		Convey("should fail without auth", func() {
			// No user ID in context
			user, err := User.Me(ctx, 0)
			So(err, ShouldNotBeNil)
			So(user, ShouldBeNil)
		})
	})
}

func (s *UserTestSuite) Test_Update() {
	Convey("Update", s.T(), func() {
		ctx := s.T().Context()
		tenantID := int64(0)
		database.Truncate(ctx, s.DB, models.TableNameUser)

		phone := "13800138004"
		resp, _ := User.LoginWithOTP(ctx, tenantID, phone, "1234")
		userID := resp.User.ID
		ctx = context.WithValue(ctx, consts.CtxKeyUser, userID)

		Convey("should update nickname and bio", func() {
			form := &user_dto.UserUpdate{
				Nickname: "NewName",
				Bio:      "New Bio",
				Gender:   consts.GenderMale,
			}
			err := User.Update(ctx, userID, form)
			So(err, ShouldBeNil)

			// Verify
			u, _ := User.Me(ctx, userID)
			So(u.Nickname, ShouldEqual, "NewName")
			So(u.Bio, ShouldEqual, "New Bio")
			So(u.Gender, ShouldEqual, consts.GenderMale)
		})
	})
}

func (s *UserTestSuite) Test_RealName() {
	Convey("RealName", s.T(), func() {
		ctx := s.T().Context()
		tenantID := int64(0)
		database.Truncate(ctx, s.DB, models.TableNameUser)

		phone := "13800138005"
		resp, _ := User.LoginWithOTP(ctx, tenantID, phone, "1234")
		userID := resp.User.ID
		ctx = context.WithValue(ctx, consts.CtxKeyUser, userID)

		Convey("should update realname status", func() {
			form := &user_dto.RealNameForm{
				Realname: "张三",
				IDCard:   "123456789012345678",
			}
			err := User.RealName(ctx, userID, form)
			So(err, ShouldBeNil)

			// Verify
			u, _ := User.Me(ctx, userID)
			So(u.IsRealNameVerified, ShouldBeTrue)
		})
	})
}

func (s *UserTestSuite) Test_GetNotifications() {
	Convey("GetNotifications", s.T(), func() {
		ctx := s.T().Context()
		tenantID := int64(0)
		database.Truncate(ctx, s.DB, models.TableNameUser, models.TableNameNotification)

		phone := "13800138006"
		resp, _ := User.LoginWithOTP(ctx, tenantID, phone, "1234")
		userID := resp.User.ID
		ctx = context.WithValue(ctx, consts.CtxKeyUser, userID)

		// Mock notifications
		_ = models.Q.Notification.WithContext(ctx).Create(&models.Notification{
			TenantID: tenantID,
			UserID:   userID,
			Type:     "system",
			Title:    "Welcome",
			Content:  "Hello World",
			IsRead:   false,
		})

		Convey("should return notifications", func() {
			list, err := User.GetNotifications(ctx, tenantID, userID, "all")
			So(err, ShouldBeNil)
			So(len(list), ShouldEqual, 1)
			So(list[0].Title, ShouldEqual, "Welcome")
			So(list[0].Type, ShouldEqual, "system")
		})
	})
}
