package services

import (
	"agent-desk/internal/models"
	"agent-desk/internal/pkg/config"
	"agent-desk/internal/pkg/dto/response"
	"agent-desk/internal/pkg/enums"
	"agent-desk/internal/pkg/errorsx"
	"agent-desk/internal/repositories"
	"agent-desk/internal/wxwork"
	"strings"
	"time"

	"github.com/mlogclub/simple/common/jsons"
	"github.com/mlogclub/simple/common/strs"
	"github.com/mlogclub/simple/sqls"
	"gorm.io/gorm"
)

var WxWorkLoginService = &wxWorkLoginService{}

type wxWorkLoginService struct {
}

func (s *wxWorkLoginService) BuildWxWorkLoginURL(next string) (string, error) {
	if !wxwork.Enabled() {
		return "", errorsx.BusinessErrorI18n(1, "error.wxwork.loginDisabled")
	}
	state, err := wxwork.CreateState(next)
	if err != nil {
		return "", err
	}
	return wxwork.BuildLoginURL(state)
}

func (s *wxWorkLoginService) BuildWxWorkQRCodeLoginURL(next string) (string, error) {
	if !wxwork.Enabled() {
		return "", errorsx.BusinessErrorI18n(1, "error.wxwork.loginDisabled")
	}
	state, err := wxwork.CreateState(next)
	if err != nil {
		return "", err
	}
	return wxwork.BuildQRCodeLoginURL(state)
}

func (s *wxWorkLoginService) LoginByWxWork(code, state string, authCfg config.AuthConfig, clientIP, userAgent string) (string, string, error) {
	next, err := wxwork.ParseState(state)
	if err != nil {
		return "", "", errorsx.UnauthorizedI18n("error.e0111")
	}
	profile, err := wxwork.GetUserDetail(code)
	if err != nil {
		return "", "", err
	}
	loginResp, err := s.loginWithWxWorkProfile(profile, authCfg, clientIP, userAgent)
	if err != nil {
		return "", "", err
	}
	ticket, err := wxwork.IssueLoginTicket(loginResp)
	if err != nil {
		return "", "", err
	}
	return ticket, next, nil
}

func (s *wxWorkLoginService) ExchangeWxWorkLoginTicket(ticket string) (*response.LoginResponse, error) {
	return wxwork.ConsumeLoginTicket(ticket)
}

func (s *wxWorkLoginService) loginWithWxWorkProfile(profile *wxwork.LoginUser, authCfg config.AuthConfig, clientIP, userAgent string) (*response.LoginResponse, error) {
	if profile == nil || strings.TrimSpace(profile.UserID) == "" {
		return nil, errorsx.BusinessErrorI18n(2, "error.wxwork.profileMissing")
	}

	var ret *response.LoginResponse
	err := sqls.WithTransaction(func(ctx *sqls.TxContext) error {
		var (
			identity = repositories.UserIdentityRepository.GetBy(ctx.Tx, enums.ThirdProviderWxWork, profile.CorpID, profile.UserID)
			user     *models.User
			err      error
		)
		if identity == nil {
			user, identity, err = s.createWxWorkUser(ctx, profile)
			if err != nil {
				return err
			}
		} else {
			if identity.Status != enums.StatusOk {
				return errorsx.BusinessErrorI18n(3, "error.wxwork.bindingDisabled")
			}
			user = repositories.UserRepository.Get(ctx.Tx, identity.UserID)
			if user == nil {
				return errorsx.BusinessErrorI18n(4, "error.wxwork.boundUserMissing")
			}
		}

		if user.Status != enums.StatusOk {
			return errorsx.UnauthorizedI18n("error.e0200")
		}

		if err = repositories.UserRepository.Updates(ctx.Tx, user.ID, map[string]any{
			"nickname":         s.resolveWxWorkNickname(user.Nickname, profile),
			"avatar":           s.resolveWxWorkAvatar(user.Avatar, profile),
			"last_login_at":    time.Now(),
			"last_login_ip":    clientIP,
			"update_user_id":   user.ID,
			"update_user_name": user.Username,
			"updated_at":       time.Now(),
		}); err != nil {
			return err
		}

		if err = repositories.UserIdentityRepository.Updates(ctx.Tx, identity.ID, map[string]any{
			"raw_profile":      jsons.ToJsonStr(profile),
			"last_auth_at":     time.Now(),
			"status":           enums.StatusOk,
			"update_user_id":   user.ID,
			"update_user_name": user.Username,
			"updated_at":       time.Now(),
		}); err != nil {
			return err
		}

		ret, err = AuthService.issueTokens(ctx, user, clientIP, userAgent, authCfg)
		if err != nil {
			return err
		}
		return nil
	})

	if err != nil {
		return nil, err
	}
	return ret, nil
}

func (s *wxWorkLoginService) createWxWorkUser(ctx *sqls.TxContext, profile *wxwork.LoginUser) (*models.User, *models.UserIdentity, error) {
	username := strings.TrimSpace(profile.UserID)
	mobile := strings.TrimSpace(profile.Mobile)
	email := strings.TrimSpace(s.firstNonEmpty(profile.Email, profile.BizMail))
	now := time.Now()

	if err := s.checkWxWorkProfile(ctx.Tx, username, mobile, email); err != nil {
		return nil, nil, err
	}

	user := &models.User{
		Username:     username,
		Nickname:     s.resolveWxWorkNickname("", profile),
		Avatar:       s.resolveWxWorkAvatar("", profile),
		Password:     "",
		PasswordSalt: "",
		Status:       enums.StatusOk,
		AuditFields: models.AuditFields{
			CreatedAt:      now,
			CreateUserID:   0,
			CreateUserName: enums.GetThirdProviderLabel(enums.ThirdProviderWxWork),
			UpdatedAt:      now,
			UpdateUserID:   0,
			UpdateUserName: enums.GetThirdProviderLabel(enums.ThirdProviderWxWork),
		},
	}
	if err := repositories.UserRepository.Create(ctx.Tx, user); err != nil {
		return nil, nil, err
	}

	identity := &models.UserIdentity{
		UserID:         user.ID,
		Provider:       enums.ThirdProviderWxWork,
		ProviderUserID: strings.TrimSpace(profile.UserID),
		ProviderCorpID: strings.TrimSpace(profile.CorpID),
		ProviderName:   enums.GetThirdProviderLabel(enums.ThirdProviderWxWork),
		RawProfile:     jsons.ToJsonStr(profile),
		Status:         enums.StatusOk,
		LastAuthAt:     &now,
		AuditFields: models.AuditFields{
			CreatedAt:      now,
			CreateUserID:   user.ID,
			CreateUserName: user.Username,
			UpdatedAt:      now,
			UpdateUserID:   user.ID,
			UpdateUserName: user.Username,
		},
	}
	if unionID := strings.TrimSpace(profile.OpenID); unionID != "" {
		identity.ProviderUnionID = &unionID
	}
	if err := repositories.UserIdentityRepository.Create(ctx.Tx, identity); err != nil {
		return nil, nil, err
	}
	return user, identity, nil
}

func (s *wxWorkLoginService) resolveWxWorkNickname(current string, profile *wxwork.LoginUser) string {
	if profile != nil {
		if name := strings.TrimSpace(profile.Name); name != "" {
			return name
		}
	}
	if current = strings.TrimSpace(current); current != "" {
		return current
	}
	if profile != nil {
		return strings.TrimSpace(profile.UserID)
	}
	return ""
}

func (s *wxWorkLoginService) resolveWxWorkAvatar(current string, profile *wxwork.LoginUser) string {
	if profile != nil {
		if avatar := strings.TrimSpace(profile.Avatar); avatar != "" {
			return avatar
		}
	}
	return strings.TrimSpace(current)
}

func (s *wxWorkLoginService) checkWxWorkProfile(tx *gorm.DB, username, mobile string, email string) error {
	if strs.IsBlank(username) {
		return errorsx.BusinessErrorI18n(5, "error.wxwork.userIDMissing")
	}
	if existing := repositories.UserRepository.GetByUsername(tx, username); existing != nil {
		return errorsx.BusinessErrorI18n(5, "error.wxwork.userIDTaken")
	}
	if strs.IsNotBlank(mobile) {
		if repositories.UserRepository.GetByMobile(tx, mobile) != nil {
			return errorsx.BusinessErrorI18n(6, "error.wxwork.mobileTaken")
		}
	}
	if strs.IsNotBlank(email) {
		if repositories.UserRepository.GetByEmail(tx, email) != nil {
			return errorsx.BusinessErrorI18n(7, "error.wxwork.emailTaken")
		}
	}
	return nil
}

func (s *wxWorkLoginService) firstNonEmpty(values ...string) string {
	for _, value := range values {
		if value = strings.TrimSpace(value); value != "" {
			return value
		}
	}
	return ""
}
