package biz

import (
	"context"
	"encoding/json"
	"fmt"
	"github.guxuan/haibei/internal/config"
	"github.guxuan/haibei/pkg/cachex"
	"github.guxuan/haibei/pkg/jwtx"
	"github.guxuan/haibei/pkg/logging"
	"go.uber.org/zap"
	"math/rand"
	"strconv"
	"time"

	"github.guxuan/haibei/internal/mods/customer/dal"
	"github.guxuan/haibei/internal/mods/customer/schema"
	"github.guxuan/haibei/pkg/errors"
	"github.guxuan/haibei/pkg/util"
)

const (
	REGISTER_SMS_CODE = "REGISTER_SMS_CODE"
	CHECK_SMS_CODE    = "CHECK_SMS_CODE"
)

// Defining the `Customer` business logic.
type Customer struct {
	Cache       cachex.Cacher
	Trans       *util.Trans
	CustomerDAL *dal.Customer
	Auth        jwtx.Auther
}

func (a *Customer) Login(ctx context.Context, formItem *schema.CustomerLoginForm) (*schema.CustomerLoginToken, error) {
	ctx = logging.NewTag(ctx, logging.TagKeyAppLogin)

	// get user info
	user, err := a.CustomerDAL.GetByWxSign(ctx, formItem.WxSign, schema.CustomerQueryOptions{})
	if err != nil {
		return nil, err
	}
	if user == nil {
		customer := &schema.Customer{}
		customer.WxSign = formItem.WxSign
		err := a.CustomerDAL.Create(ctx, customer)
		if err != nil {
			return nil, err
		}
		user = customer
	}
	userID := fmt.Sprintf("%d", user.ID)
	var subject jwtx.JwtSubject
	subject.ID = userID
	subject.Typer = "APP"
	marshal, err := json.Marshal(subject)
	if err != nil {
		return nil, err
	}
	// generate token
	ctx = logging.NewUserID(ctx, userID)

	userCache := util.UserCache{RoleIDs: []string{}}
	err = a.Cache.Set(ctx, config.CacheNSForUser, userID, userCache.String(),
		time.Duration(config.C.Dictionary.UserCacheExp)*time.Hour)
	if err != nil {
		logging.Context(ctx).Error("Failed to set cache", zap.Error(err))
	}
	logging.Context(ctx).Info("Login success", zap.String("WxSign", formItem.WxSign))
	reload, err := a.genCustomerToken(ctx, string(marshal))
	reload.Phone = user.Phone
	return reload, err
}
func (a *Customer) BindPhone(ctx context.Context, formItem *schema.CustomerBindPhoneForm) (bool, error) {
	userID := util.FromUserID(ctx)
	num, err := strconv.ParseUint(fmt.Sprintf("%s", userID), 10, 64)
	if err != nil {
		fmt.Println("转换错误:", err)
		return false, err
	}
	userIDNum := uint(num)
	code, b, err := a.Cache.Get(ctx, REGISTER_SMS_CODE, formItem.Phone)
	if err != nil {
		return false, err
	}
	if !b {
		return false, errors.NotFound("", "验证码过期！")
	}
	if code != formItem.Code {
		return false, errors.NotFound("", "验证码失败！")
	}
	get, err := a.CustomerDAL.Get(ctx, userIDNum, schema.CustomerQueryOptions{})
	if err != nil || get == nil {
		return false, err
	}
	get.Phone = formItem.Phone
	get.Avatar = formItem.Avatar
	err = a.CustomerDAL.Update(ctx, get)
	if err != nil {
		return false, err
	}
	return true, nil
}

func (a *Customer) SendSms(ctx context.Context, phone string) (string, error) {
	code := generateVerificationCode()
	exists, err := a.Cache.Exists(ctx, REGISTER_SMS_CODE, phone)
	if err != nil {
		return "", err
	}
	if exists {
		return "", errors.NotFound("", "验证码不能重复获取 等待90秒")
	}
	err = sendVerificationCode(phone, code)
	if err != nil {
		return "", err
	}
	err = a.Cache.Set(ctx, REGISTER_SMS_CODE, phone, code, 90*time.Second)
	if err != nil {
		return "", err
	}
	return code, nil

}
func sendVerificationCode(phone, code string) error {
	// 这里应该是调用短信服务商的API发送短信
	// 这里只是模拟
	fmt.Printf("向手机号 %s 发送验证码: %s\n", phone, code)
	return nil
}
func generateVerificationCode() string {
	rand.Seed(time.Now().UnixNano())
	return fmt.Sprintf("%06d", rand.Intn(1000000))
}
func (a *Customer) genCustomerToken(ctx context.Context, suject string) (*schema.CustomerLoginToken, error) {
	token, err := a.Auth.GenerateToken(ctx, suject)
	if err != nil {
		return nil, err
	}

	tokenBuf, err := token.EncodeToJSON()
	if err != nil {
		return nil, err
	}
	logging.Context(ctx).Info("Generate user token", zap.Any("token", string(tokenBuf)))

	return &schema.CustomerLoginToken{
		AccessToken: token.GetAccessToken(),
		TokenType:   token.GetTokenType(),
		ExpiresAt:   token.GetExpiresAt(),
	}, nil
}

// Query customers from the data access object based on the provided parameters and options.
func (a *Customer) Query(ctx context.Context, params schema.CustomerQueryParam) (*schema.CustomerQueryResult, error) {
	params.Pagination = true

	result, err := a.CustomerDAL.Query(ctx, params, schema.CustomerQueryOptions{
		QueryOptions: util.QueryOptions{
			OrderFields: []util.OrderByParam{
				{Field: "created_at", Direction: util.DESC},
			},
		},
	})
	if err != nil {
		return nil, err
	}
	return result, nil
}

// Get the specified customer from the data access object.
func (a *Customer) Get(ctx context.Context, id uint) (*schema.Customer, error) {
	customer, err := a.CustomerDAL.Get(ctx, id)
	if err != nil {
		return nil, err
	} else if customer == nil {
		return nil, errors.NotFound("", "Customer not found")
	}
	return customer, nil
}

// Create a new customer in the data access object.
func (a *Customer) Create(ctx context.Context, formItem *schema.CustomerForm) (*schema.Customer, error) {
	customer := &schema.Customer{}

	if err := formItem.FillTo(customer); err != nil {
		return nil, err
	}

	err := a.Trans.Exec(ctx, func(ctx context.Context) error {
		if err := a.CustomerDAL.Create(ctx, customer); err != nil {
			return err
		}
		return nil
	})
	if err != nil {
		return nil, err
	}
	return customer, nil
}

// Update the specified customer in the data access object.
func (a *Customer) Update(ctx context.Context, id uint, formItem *schema.CustomerForm) error {
	customer, err := a.CustomerDAL.Get(ctx, id)
	if err != nil {
		return err
	} else if customer == nil {
		return errors.NotFound("", "Customer not found")
	}

	if err := formItem.FillTo(customer); err != nil {
		return err
	}
	customer.UpdatedAt = time.Now()

	return a.Trans.Exec(ctx, func(ctx context.Context) error {
		if err := a.CustomerDAL.Update(ctx, customer); err != nil {
			return err
		}
		return nil
	})
}

// Delete the specified customer from the data access object.
func (a *Customer) Delete(ctx context.Context, id uint) error {
	exists, err := a.CustomerDAL.Exists(ctx, id)
	if err != nil {
		return err
	} else if !exists {
		return errors.NotFound("", "Customer not found")
	}

	return a.Trans.Exec(ctx, func(ctx context.Context) error {
		if err := a.CustomerDAL.Delete(ctx, id); err != nil {
			return err
		}
		return nil
	})
}
