package core

import (
	"fmt"
	"intent-system/pkg/config"
	"intent-system/pkg/dal/dao"
	"intent-system/pkg/dal/models"
	"intent-system/pkg/email"
	"intent-system/pkg/itypes"
	"intent-system/pkg/proto"
	"intent-system/pkg/sessions"
	"intent-system/pkg/utils"
	"strings"
	"time"

	"github.com/civet148/log"
	"github.com/civet148/sqlca/v2"
	"github.com/gin-gonic/gin"
)

type CustomerCore struct {
	db  *sqlca.Engine
	cfg *config.Config

	loginDAO      *dao.LoginDAO
	dictionaryDAO *dao.DictionaryDAO
	inviteCodeDAO *dao.InviteCodeDAO
	customerDAO   *dao.CustomerDAO
	subscriberDAO *dao.SubscriberDAO
	templateDAO   *dao.EmailTemplateDAO
}

func NewCustomerCore(cfg *config.Config, db *sqlca.Engine) *CustomerCore {
	return &CustomerCore{
		db:            db,
		cfg:           cfg,
		loginDAO:      dao.NewLoginDAO(db),
		dictionaryDAO: dao.NewDictionaryDAO(db),
		inviteCodeDAO: dao.NewInviteCodeDAO(db),
		customerDAO:   dao.NewCustomerDAO(db),
		subscriberDAO: dao.NewSubscriberDAO(db),
		templateDAO:   dao.NewEmailTemplateDAO(db),
	}
}

func (m *CustomerCore) UserRegister(req *proto.CustomerRegisterReq) (resp *proto.CustomerRegisterResp, code itypes.BizCode) {
	var do *models.CustomerDO
	do, err := m.customerDAO.SelectCustomerByEmail(req.Email)
	if err != nil {
		log.Errorf(err.Error())
		return nil, itypes.NewBizCodeDatabaseError()
	}
	if do.Id != 0 && !do.Deleted {
		return nil, itypes.NewBizCode(itypes.CODE_ALREADY_EXIST, "email already registered")
	}

	var skipVerifyRegCode bool
	if m.cfg.Debug && req.RegCode == "888888" {
		skipVerifyRegCode = true
	}
	if !skipVerifyRegCode {
		ok, err := m.inviteCodeDAO.CheckUserInviteCode(req.UserName, req.RegCode)
		if err != nil {
			log.Errorf(err.Error())
			return nil, itypes.NewBizCodeDatabaseError()
		}
		if !ok {
			return nil, itypes.NewBizCode(itypes.CODE_INVALID_AUTH_CODE, "invalid auth code")
		}
		m.inviteCodeDAO.UpdateByUserAndInviteCode(req.UserName, req.RegCode)
	}
	code = m.createRegisterUser(req.UserName, req.Password, req.Email)
	if !code.Ok() {
		return nil, code
	}
	return &proto.CustomerRegisterResp{}, itypes.BizOK
}

func (m *CustomerCore) UserNameRegister(req *proto.CustomerURegisterReq) (resp *proto.CustomerRegisterResp, code itypes.BizCode) {
	// 仅检查用户名是否已经被注册
	do, err := m.customerDAO.SelectCustomerByName(req.UserName)
	if err != nil {
		log.Errorf("Database error: %v", err)
		return nil, itypes.NewBizCodeDatabaseError()
	}
	if do.Id != 0 && !do.Deleted {
		return nil, itypes.NewBizCode(itypes.CODE_ALREADY_EXIST, "Username already registered")
	}

	// 直接创建用户，无需邮箱检查或验证码验证
	code = m.createURegisterUser(req.UserName, req.Password, req.Referral)
	if !code.Ok() {
		return nil, code
	}

	// 返回成功响应
	return &proto.CustomerRegisterResp{}, itypes.BizOK
}

func (m *CustomerCore) createRegisterUser(strUserName, strPassword, strEmail string) (code itypes.BizCode) {
	var err error
	var strSalt = utils.GenerateSalt()
	do := &models.CustomerDO{
		UserName:   strUserName,
		Password:   strPassword,
		Salt:       strSalt,
		UserAlias:  strUserName,
		IsAdmin:    false,
		Email:      strEmail,
		CreateUser: SystemName,
		EditUser:   SystemName,
		LoginTime:  utils.Now64(),
		Deleted:    false,
		State:      dao.CustomerState_Enabled,
	}
	if _, err = m.customerDAO.Insert(do); err != nil {
		err = log.Errorf(err.Error())
		return itypes.NewBizCode(itypes.CODE_INTERNAL_SERVER_ERROR, err.Error())
	}
	return itypes.BizOK
}

func (m *CustomerCore) createURegisterUser(strUserName, strPassword, strReferrer string) (code itypes.BizCode) {
	var err error
	var strSalt = utils.GenerateSalt()
	do := &models.CustomerDO{
		UserName:   strUserName,
		Password:   strPassword,
		Salt:       strSalt,
		UserAlias:  strUserName,
		IsAdmin:    false,
		Referrer:   strReferrer,
		CreateUser: SystemName,
		EditUser:   SystemName,
		LoginTime:  utils.Now64(),
		Deleted:    false,
		State:      dao.CustomerState_Enabled,
	}
	if _, err = m.customerDAO.Insert(do); err != nil {
		err = log.Errorf(err.Error())
		return itypes.NewBizCode(itypes.CODE_INTERNAL_SERVER_ERROR, err.Error())
	}
	return itypes.BizOK
}

func (m *CustomerCore) CustomerLogin(req *proto.CustomerLoginReq, strIP string) (customer *models.CustomerDO, code itypes.BizCode) {

	var err error
	var strUserName = req.UserName
	var strPassword = req.Password
	if customer, err = m.customerDAO.SelectCustomerByName(req.UserName); err != nil {
		log.Errorf("query [user name] from table error [%s]", err.Error())
		return nil, itypes.NewBizCode(itypes.CODE_INVALID_USER_OR_PASSWORD)
	}

	if customer == nil || customer.GetId() == 0 {
		if customer, err = m.customerDAO.SelectCustomerByEmail(strUserName); err != nil {
			log.Errorf("query [email] from table error [%s]", err.Error())
			return nil, itypes.NewBizCode(itypes.CODE_DATABASE_ERROR)
		}
		if customer == nil || customer.GetId() == 0 {
			log.Errorf("user name/email [%s] data not found in db", strUserName)
			return nil, itypes.NewBizCode(itypes.CODE_INVALID_USER_OR_PASSWORD)
		}
	}

	if customer.State == dao.UserState_Disabled {
		log.Errorf("user name/email [%s] account was disabled", strUserName)
		return nil, itypes.NewBizCode(itypes.CODE_ACCOUNT_BANNED)
	}

	customer.LoginIp = strIP
	customer.LoginTime = time.Now().Unix()
	if err = m.customerDAO.UpdateByName(customer, models.USER_COLUMN_LOGIN_IP, models.USER_COLUMN_LOGIN_TIME); err != nil {
		log.Errorf("update user [%s] login ip error [%s]", strUserName, strIP)
		return nil, itypes.NewBizCodeDatabaseError()
	}

	if strPassword != customer.Password {
		err = log.Errorf("user name [%s] password verify failed, password [%s] not match", strUserName, strPassword)
		return nil, itypes.NewBizCode(itypes.CODE_INVALID_USER_OR_PASSWORD, err.Error())
	}
	_, _ = m.loginDAO.Insert(&models.LoginDO{
		UserId:    customer.GetId(),
		LoginType: dao.LoginType_Customer,
		LoginIp:   strIP,
		LoginAddr: "",
	})
	return customer, itypes.BizOK
}

func (m *CustomerCore) CustomerEdit(ctx *itypes.Context, req *proto.CustomerEditReq) (resp *proto.CustomerEditResp, code itypes.BizCode) {
	var strEmail string
	if ctx == nil {
		if req.Email == "" {
			return nil, itypes.NewBizCode(itypes.CODE_INVALID_PARAMS, "email must not be empty")
		}
		strEmail = req.Email
	} else {
		strEmail = ctx.GetEmail()
	}
	_, err := m.customerDAO.UpdateByEmail(&models.CustomerDO{
		Email:     strEmail,
		FirstName: req.FirstName,
		LastName:  req.LastName,
		Title:     req.Title,
		Company:   req.Company,
	}, models.CUSTOMER_COLUMN_FIRST_NAME, models.CUSTOMER_COLUMN_LAST_NAME, models.CUSTOMER_COLUMN_TITLE, models.CUSTOMER_COLUMN_COMPANY)
	if err != nil {
		log.Errorf(err.Error())
		return nil, itypes.NewBizCodeDatabaseError(err.Error())
	}
	return &proto.CustomerEditResp{}, itypes.BizOK
}

func (m *CustomerCore) CustomerSubscriber(strEmail string) (do *models.SubscriberDO, code itypes.BizCode) {
	var err error
	do, err = m.subscriberDAO.QueryByEmail(strEmail)
	if err != nil {
		log.Errorf(err.Error())
		return nil, itypes.NewBizCodeDatabaseError(err.Error())
	}
	if len(do.Tags) == 0 {
		do.Tags = make([]string, 0)
	}
	return do, code
}

func (m *CustomerCore) CustomerLogout(c *gin.Context) (resp *proto.CustomerLogoutResp, code itypes.BizCode) {
	sessions.RemoveContext(c)
	return &proto.CustomerLogoutResp{}, itypes.BizOK
}

func (m *CustomerCore) CustomerSubInfo(ctx *itypes.Context, req *proto.CustomerSubInfoReq) (resp *proto.CustomerSubInfoResp, code itypes.BizCode) {
	var strEmail string
	if ctx == nil {
		if req.Email == "" {
			return nil, itypes.NewBizCode(itypes.CODE_INVALID_PARAMS, "email must not be empty")
		}
		strEmail = req.Email
	} else {
		strEmail = ctx.GetEmail()
	}

	customer, err := m.customerDAO.SelectCustomerByEmail(strEmail)
	if err != nil {
		log.Errorf(err.Error())
		return nil, itypes.NewBizCodeDatabaseError(err.Error())
	}
	do, err := m.subscriberDAO.QueryByEmail(strEmail)
	if err != nil {
		log.Errorf(err.Error())
		return nil, itypes.NewBizCodeDatabaseError(err.Error())
	}
	isSubscribed := do.Id != 0

	return &proto.CustomerSubInfoResp{
		IsSubscribed: isSubscribed,
		Tags:         do.Tags,
		FirstName:    customer.FirstName,
		LastName:     customer.LastName,
		Title:        customer.Title,
		Company:      customer.Company,
	}, itypes.BizOK
}

func (m *CustomerCore) CustomerSubscribe(ctx *itypes.Context, req *proto.CustomerSubscribeReq) (resp *proto.CustomerSubscribeResp, code itypes.BizCode) {
	var userId int32
	var strEmail string
	if ctx == nil {
		if req.Email == "" {
			return nil, itypes.NewBizCode(itypes.CODE_INVALID_PARAMS, "email must not be empty")
		}
		strEmail = req.Email
	} else {
		userId = ctx.UserId()
		strEmail = ctx.GetEmail()
	}
	sub, err := m.subscriberDAO.QueryByEmail(strEmail)
	if err != nil {
		log.Errorf(err.Error())
		return nil, itypes.NewBizCodeDatabaseError(err.Error())
	}
	if sub.Id == 0 {
		err = m.sendSubscriptionWelcomeEmail(req.FirstName, strEmail, req.Language)
		if err != nil {
			err = log.Errorf("send welcome email error [%s]", err.Error())
			return nil, itypes.NewBizCode(itypes.CODE_INVALID_PARAMS, err.Error())
		}
	}

	_, err = m.subscriberDAO.Upsert(&models.SubscriberDO{
		CustomerId: userId,
		Email:      strEmail,
		Tags:       req.Tags,
		IsDeleted:  false,
	}, models.SUBSCRIBER_COLUMN_IS_DELETED, models.SUBSCRIBER_COLUMN_TAGS, models.SUBSCRIBER_COLUMN_EXTRA_DATA)
	if err != nil {
		log.Errorf(err.Error())
		return nil, itypes.NewBizCodeDatabaseError(err.Error())
	}
	_, err = m.customerDAO.UpdateByEmail(&models.CustomerDO{
		FirstName:    req.FirstName,
		IsSubscribed: true,
		Deleted:      false,
	}, models.CUSTOMER_COLUMN_IS_SUBSCRIBED, models.CUSTOMER_COLUMN_FIRST_NAME, models.CUSTOMER_COLUMN_DELETED)
	if err != nil {
		log.Errorf(err.Error())
		return nil, itypes.NewBizCodeDatabaseError(err.Error())
	}
	return &proto.CustomerSubscribeResp{}, itypes.BizOK
}

func (m *CustomerCore) sendSubscriptionWelcomeEmail(strUser, strEmail, strLang string) (err error) {
	var strContent string
	var strSubject = email.SubscriptionWelcomeSubject
	var strTemplate = email.SubscriptionWelcomeTemplate
	var do *models.TemplateDO
	do, err = m.templateDAO.QueryByTypeLang(models.TemplateType_SubscriptionWelcome, strLang)
	if do.Id != 0 {
		strSubject = do.Subject
		strTemplate = do.Content
	} else {
		log.Warnf("no subscription welcome template found, use default")
	}
	var cfg *email.EmailConfig
	cfg, err = m.dictionaryDAO.SelectEmailConfig()
	if err != nil {
		return log.Errorf(err.Error())
	}
	strReplace := fmt.Sprintf("[%s]", strUser)
	strContent = strings.Replace(strTemplate, "[%s]", strReplace, -1)
	err = email.SendEmail(cfg, strSubject, strContent, strEmail)
	if err != nil {
		return log.Errorf(err.Error())
	}
	return nil
}

func (m *CustomerCore) CustomerUnsubscribe(ctx *itypes.Context, req *proto.CustomerUnsubscribeReq) (resp *proto.CustomerUnsubscribeResp, code itypes.BizCode) {
	var userId int32
	var strEmail string
	if ctx == nil {
		if req.Email == "" {
			return nil, itypes.NewBizCode(itypes.CODE_INVALID_PARAMS, "email must not be empty")
		}
		strEmail = req.Email
	} else {
		userId = ctx.UserId()
		strEmail = ctx.GetEmail()
	}
	_, err := m.subscriberDAO.DeleteByUserEmail(&models.SubscriberDO{
		CustomerId: userId,
		Email:      strEmail,
		IsDeleted:  true,
		ExtraData: models.SubscriberExtraData{
			UnsubscribeReason: req.Reason,
		},
	}, models.SUBSCRIBER_COLUMN_IS_DELETED, models.SUBSCRIBER_COLUMN_EXTRA_DATA)
	if err != nil {
		log.Errorf(err.Error())
		return nil, itypes.NewBizCodeDatabaseError(err.Error())
	}
	if userId != 0 {
		_, err = m.customerDAO.Update(&models.CustomerDO{
			Id:           userId,
			IsSubscribed: false,
		}, models.CUSTOMER_COLUMN_IS_SUBSCRIBED)
		if err != nil {
			log.Errorf(err.Error())
			return nil, itypes.NewBizCodeDatabaseError(err.Error())
		}
	}
	return &proto.CustomerUnsubscribeResp{}, itypes.BizOK
}

func (m *CustomerCore) CustomerList(ctx *itypes.Context, req *proto.CustomerListReq) (resp *proto.CustomerListResp, total int64, code itypes.BizCode) {
	var err error
	var dos []*models.CustomerDO
	dos, total, err = m.customerDAO.QueryList(req.PageNo, req.PageSize, req.Id, req.Email)
	if err != nil {
		log.Errorf(err.Error())
		return nil, 0, itypes.NewBizCodeDatabaseError(err.Error())
	}
	return &proto.CustomerListResp{
		List: dos,
	}, total, itypes.BizOK
}
