package user

import (
	"context"
	"strings"
	"time"

	roomv1 "hyapp.local/api/proto/room/v1"
	"hyapp/pkg/appcode"
	"hyapp/pkg/idgen"
	"hyapp/pkg/xerr"
	userdomain "hyapp/services/user-service/internal/domain/user"
)

const userStatusRevokeReason = "USER_STATUS_CHANGED"

const (
	OperatorTypeAdmin   = "admin"
	OperatorTypeAppUser = "app_user"
	OperatorTypeSystem  = "system"
)

// UserStatusCommand 是用户治理入口的服务层命令。
type UserStatusCommand struct {
	AppCode        string
	TargetUserID   int64
	Status         userdomain.Status
	OperatorType   string
	OperatorUserID int64
	Reason         string
	RequestID      string
	NowMs          int64
}

// UserStatusPersistenceResult 是状态事务提交后的数据库事实。
type UserStatusPersistenceResult struct {
	User              userdomain.User
	RevokedSessionIDs []string
}

// UserStatusResult 汇总状态事实和封禁后需要立即执行的外部副作用结果。
type UserStatusResult struct {
	User                userdomain.User
	RevokedSessionCount int64
	AccessTokenRevoked  bool
	AccessTokenError    string
	IMKicked            bool
	IMKickError         string
	RoomEvicted         bool
	RoomID              string
	RTCKicked           bool
	RTCKickError        string
	RoomEvictError      string
}

// SetUserStatus 是封禁、停用和恢复用户的通用用例入口。
// 非 active 状态会先提交用户状态和 session 吊销事实，再执行 Redis/IM/Room 的可重试副作用。
func (s *Service) SetUserStatus(ctx context.Context, command UserStatusCommand) (UserStatusResult, error) {
	if command.TargetUserID <= 0 {
		return UserStatusResult{}, xerr.New(xerr.InvalidArgument, "target_user_id is required")
	}
	command.Status = normalizeUserStatus(command.Status)
	if !validUserStatus(command.Status) {
		return UserStatusResult{}, xerr.New(xerr.InvalidArgument, "status is invalid")
	}
	if s.moderationRepository == nil {
		return UserStatusResult{}, xerr.New(xerr.Unavailable, "moderation repository is not configured")
	}
	if command.NowMs <= 0 {
		command.NowMs = s.now().UTC().UnixMilli()
	}
	command.AppCode = appcode.Normalize(command.AppCode)
	if command.AppCode == "" {
		command.AppCode = appcode.FromContext(ctx)
	}
	command.Reason = normalizeModerationReason(command.Reason, command.Status)
	command.OperatorType = normalizeOperatorType(command.OperatorType)

	persisted, err := s.moderationRepository.SetUserStatus(ctx, command)
	if err != nil {
		return UserStatusResult{}, err
	}
	result := UserStatusResult{
		User:                persisted.User,
		RevokedSessionCount: int64(len(persisted.RevokedSessionIDs)),
		AccessTokenRevoked:  true,
	}
	if command.Status == userdomain.StatusActive {
		// 恢复 active 只改变准入状态，不重建旧 session、IM 登录或房间 presence。
		return result, nil
	}

	result.AccessTokenRevoked, result.AccessTokenError = s.writeSessionDenylist(ctx, command, persisted.RevokedSessionIDs)
	result.IMKicked, result.IMKickError = s.kickIMLogin(ctx, command.TargetUserID)
	result.RoomEvicted, result.RoomID, result.RTCKicked, result.RTCKickError, result.RoomEvictError = s.evictCurrentRoom(ctx, command)
	return result, nil
}

func (s *Service) writeSessionDenylist(ctx context.Context, command UserStatusCommand, sessionIDs []string) (bool, string) {
	if len(sessionIDs) == 0 {
		return true, ""
	}
	if s.sessionDenylist == nil {
		return false, "session denylist is not configured"
	}
	ttl := s.sessionDenylistTTL
	if ttl <= 0 {
		ttl = time.Hour
	}
	failed := 0
	var firstError string
	for _, sessionID := range sessionIDs {
		sessionID = strings.TrimSpace(sessionID)
		if sessionID == "" {
			continue
		}
		// 单个 Redis 写入失败不能回滚已经提交的封禁事实；调用方可通过重复封禁补偿剩余 session。
		if err := s.sessionDenylist.SetRevokedSession(ctx, command.AppCode, sessionID, userStatusRevokeReason, ttl); err != nil {
			failed++
			if firstError == "" {
				firstError = err.Error()
			}
		}
	}
	if failed > 0 {
		return false, firstError
	}
	return true, ""
}

func (s *Service) kickIMLogin(ctx context.Context, userID int64) (bool, string) {
	if s.imLoginKicker == nil {
		return false, ""
	}
	if err := s.imLoginKicker.KickUser(ctx, userID); err != nil {
		return false, err.Error()
	}
	return true, ""
}

func (s *Service) evictCurrentRoom(ctx context.Context, command UserStatusCommand) (bool, string, bool, string, string) {
	if s.roomEvictor == nil {
		return false, "", false, "", ""
	}
	resp, err := s.roomEvictor.SystemEvictUser(ctx, &roomv1.SystemEvictUserRequest{
		Meta: &roomv1.RequestMeta{
			RequestId:   command.RequestID,
			CommandId:   idgen.New("cmd_user_status_evict"),
			ActorUserId: command.TargetUserID,
			SentAtMs:    command.NowMs,
			AppCode:     command.AppCode,
		},
		TargetUserId:   command.TargetUserID,
		OperatorUserId: command.OperatorUserID,
		Reason:         command.Reason,
		BanFromRoom:    true,
	})
	if err != nil {
		return false, "", false, "", err.Error()
	}
	return resp.GetHadCurrentRoom() && resp.GetResult().GetApplied(), resp.GetRoomId(), resp.GetRtcKicked(), resp.GetRtcKickError(), ""
}

func normalizeUserStatus(status userdomain.Status) userdomain.Status {
	return userdomain.Status(strings.ToLower(strings.TrimSpace(string(status))))
}

func validUserStatus(status userdomain.Status) bool {
	return status == userdomain.StatusActive || status == userdomain.StatusDisabled || status == userdomain.StatusBanned
}

func normalizeOperatorType(operatorType string) string {
	switch strings.ToLower(strings.TrimSpace(operatorType)) {
	case OperatorTypeAdmin:
		return OperatorTypeAdmin
	case OperatorTypeAppUser:
		return OperatorTypeAppUser
	default:
		return OperatorTypeSystem
	}
}

func normalizeModerationReason(reason string, status userdomain.Status) string {
	reason = strings.TrimSpace(reason)
	if reason != "" {
		return reason
	}
	switch status {
	case userdomain.StatusBanned:
		return "user_banned"
	case userdomain.StatusDisabled:
		return "user_disabled"
	default:
		return "user_status_active"
	}
}
