package user

import (
	"context"
	"time"

	modelUser "github.com/perfect-panel/server/internal/model/user"
	"github.com/perfect-panel/server/internal/svc"
	"github.com/perfect-panel/server/pkg/logger"
	"github.com/perfect-panel/server/pkg/xerr"
	"github.com/pkg/errors"
	"gorm.io/gorm"
	"gorm.io/gorm/clause"
)

type accountMergeResult struct {
	OwnerUserID       int64
	DeviceUserID      int64
	MovedDevices      []modelUser.Device
	RemovedSubscribes []modelUser.Subscribe
}

type accountMergeHelper struct {
	ctx    context.Context
	svcCtx *svc.ServiceContext
}

func newAccountMergeHelper(ctx context.Context, svcCtx *svc.ServiceContext) *accountMergeHelper {
	return &accountMergeHelper{
		ctx:    ctx,
		svcCtx: svcCtx,
	}
}

func (h *accountMergeHelper) mergeIntoOwner(ownerUserID, deviceUserID int64, source string) (*accountMergeResult, error) {
	if ownerUserID == 0 || deviceUserID == 0 {
		return nil, errors.Wrapf(xerr.NewErrCode(xerr.InvalidParams), "merge user id is empty")
	}
	if ownerUserID == deviceUserID {
		return nil, errors.Wrapf(xerr.NewErrCode(xerr.InvalidParams), "cannot merge same user id")
	}

	result := &accountMergeResult{
		OwnerUserID:  ownerUserID,
		DeviceUserID: deviceUserID,
	}

	// Capture device user's auth methods BEFORE the transaction migrates them
	deviceAuthMethods, _ := h.svcCtx.UserModel.FindUserAuthMethods(h.ctx, deviceUserID)

	err := h.svcCtx.DB.WithContext(h.ctx).Transaction(func(tx *gorm.DB) error {
		var owner modelUser.User
		if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).
			Where("id = ?", ownerUserID).
			First(&owner).Error; err != nil {
			if errors.Is(err, gorm.ErrRecordNotFound) {
				return errors.Wrapf(xerr.NewErrCode(xerr.UserNotExist), "owner user %d not found", ownerUserID)
			}
			return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "query owner user failed")
		}

		var device modelUser.User
		if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).
			Where("id = ?", deviceUserID).
			First(&device).Error; err != nil {
			if errors.Is(err, gorm.ErrRecordNotFound) {
				return errors.Wrapf(xerr.NewErrCode(xerr.UserNotExist), "device user %d not found", deviceUserID)
			}
			return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "query device user failed")
		}

		exitHelper := newFamilyExitHelper(h.ctx, h.svcCtx)
		if err := exitHelper.removeUserFromActiveFamily(tx, deviceUserID, false); err != nil {
			return err
		}

		removedSubscribes, err := transferMemberSubscribesToOwner(tx, deviceUserID, ownerUserID)
		if err != nil {
			return err
		}
		result.RemovedSubscribes = removedSubscribes

		var devices []modelUser.Device
		if err := tx.Where("user_id = ?", deviceUserID).Find(&devices).Error; err != nil {
			return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseQueryError), "query device list failed")
		}
		if len(devices) > 0 {
			if err := tx.Model(&modelUser.Device{}).
				Where("user_id = ?", deviceUserID).
				Updates(map[string]interface{}{
					"user_id":    ownerUserID,
					"updated_at": time.Now(),
				}).Error; err != nil {
				return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseUpdateError), "update device ownership failed")
			}
		}
		result.MovedDevices = devices

		if err := tx.Model(&modelUser.AuthMethods{}).
			Where("user_id = ?", deviceUserID).
			Update("user_id", ownerUserID).Error; err != nil {
			return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseUpdateError), "migrate auth methods failed")
		}

		if err := tx.Model(&modelUser.User{}).
			Where("id = ?", deviceUserID).
			Update("enable", false).Error; err != nil {
			return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseUpdateError), "disable device user failed")
		}
		if err := tx.Where("id = ?", deviceUserID).Delete(&modelUser.User{}).Error; err != nil {
			return errors.Wrapf(xerr.NewErrCode(xerr.DatabaseUpdateError), "soft delete device user failed")
		}

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

	if err := h.clearCaches(result, deviceAuthMethods); err != nil {
		return nil, err
	}

	logger.WithContext(h.ctx).Infow("device account merged into owner",
		logger.Field("owner_user_id", ownerUserID),
		logger.Field("device_user_id", deviceUserID),
		logger.Field("moved_devices", len(result.MovedDevices)),
		logger.Field("removed_subscribes", len(result.RemovedSubscribes)),
		logger.Field("source", source),
	)

	return result, nil
}

func (h *accountMergeHelper) clearCaches(result *accountMergeResult, deviceAuthMethods []*modelUser.AuthMethods) error {
	if result == nil {
		return nil
	}

	// Fetch owner user with AuthMethods for proper cache key generation
	var users []*modelUser.User
	if u, err := h.svcCtx.UserModel.FindOne(h.ctx, result.OwnerUserID); err == nil {
		users = append(users, u)
	}
	// For device user, FindOne won't have AuthMethods anymore (migrated in tx),
	// so we build a minimal User with the pre-captured auth methods
	deviceUser := &modelUser.User{Id: result.DeviceUserID}
	if len(deviceAuthMethods) > 0 {
		authMethods := make([]modelUser.AuthMethods, len(deviceAuthMethods))
		for i, am := range deviceAuthMethods {
			authMethods[i] = *am
		}
		deviceUser.AuthMethods = authMethods
	}
	users = append(users, deviceUser)

	if len(users) > 0 {
		if err := h.svcCtx.UserModel.ClearUserCache(h.ctx, users...); err != nil {
			return err
		}
	}

	if len(result.MovedDevices) > 0 {
		deviceModels := make([]*modelUser.Device, 0, len(result.MovedDevices))
		for i := range result.MovedDevices {
			device := result.MovedDevices[i]
			deviceModels = append(deviceModels, &device)
		}
		if err := h.svcCtx.UserModel.ClearDeviceCache(h.ctx, deviceModels...); err != nil {
			return err
		}
	}

	if len(result.RemovedSubscribes) > 0 {
		familyHelper := newFamilyBindingHelper(h.ctx, h.svcCtx)
		if err := familyHelper.clearRemovedMemberSubscribeCache(result.RemovedSubscribes); err != nil {
			return err
		}
	}

	return nil
}
