package grpc

import (
	"context"
	"time"

	userv1 "hyapp.local/api/proto/user/v1"
	"hyapp/pkg/xerr"
	userdomain "hyapp/services/user-service/internal/domain/user"
	authservice "hyapp/services/user-service/internal/service/auth"
	mictimeservice "hyapp/services/user-service/internal/service/mictime"
	roomtimeservice "hyapp/services/user-service/internal/service/roomtime"
	userservice "hyapp/services/user-service/internal/service/user"
)

// ProcessLoginIPRiskBatch 处理一批登录后 IP 风控任务，任务 claim 和 session revoke 仍在 user-service 内完成。
func (s *Server) ProcessLoginIPRiskBatch(ctx context.Context, req *userv1.CronBatchRequest) (*userv1.CronBatchResponse, error) {
	ctx = contextWithApp(ctx, req.GetMeta())
	if s.authSvc == nil {
		return nil, xerr.ToGRPCError(xerr.New(xerr.Unavailable, "auth service is not configured"))
	}
	processed, err := s.authSvc.ProcessLoginIPRiskBatch(ctx, authservice.LoginIPRiskWorkerOptions{
		WorkerID:  req.GetWorkerId(),
		LockTTL:   durationFromMillis(req.GetLockTtlMs()),
		BatchSize: int(req.GetBatchSize()),
	})
	if err != nil {
		return nil, xerr.ToGRPCError(err)
	}
	return &userv1.CronBatchResponse{
		ClaimedCount:   int32(processed),
		ProcessedCount: int32(processed),
		SuccessCount:   int32(processed),
		HasMore:        processed >= normalizedBatchSize(req.GetBatchSize(), 100),
	}, nil
}

// ProcessRegionRebuildBatch 处理一个区域重算分页任务，按 app_code 作用域 claim。
func (s *Server) ProcessRegionRebuildBatch(ctx context.Context, req *userv1.CronBatchRequest) (*userv1.CronBatchResponse, error) {
	ctx = contextWithApp(ctx, req.GetMeta())
	if s.userSvc == nil {
		return nil, xerr.ToGRPCError(xerr.New(xerr.Unavailable, "user service is not configured"))
	}
	result, err := s.userSvc.ProcessNextRegionRebuildTask(ctx, userservice.RegionRebuildWorkerOptions{
		WorkerID:  req.GetWorkerId(),
		LockTTL:   durationFromMillis(req.GetLockTtlMs()),
		BatchSize: int(req.GetBatchSize()),
	})
	if err != nil {
		return nil, xerr.ToGRPCError(err)
	}
	if result.NoTask {
		return &userv1.CronBatchResponse{}, nil
	}
	return &userv1.CronBatchResponse{
		ClaimedCount:   1,
		ProcessedCount: int32(result.ProcessedUsers),
		SuccessCount:   1,
		HasMore:        result.Status == userdomain.RegionRebuildTaskStatusPending,
	}, nil
}

// CompensateMicOpenSessions 关闭异常长时间未收到 down 事件的用户麦位统计 session，不反写 Room Cell。
func (s *Server) CompensateMicOpenSessions(ctx context.Context, req *userv1.CronBatchRequest) (*userv1.CronBatchResponse, error) {
	ctx = contextWithApp(ctx, req.GetMeta())
	if s.micTimeSvc == nil {
		return nil, xerr.ToGRPCError(xerr.New(xerr.Unavailable, "mic time service is not configured"))
	}
	result, err := s.micTimeSvc.ProcessOpenSessionCompensationBatch(ctx, mictimeservice.CompensationWorkerOptions{
		WorkerID:                req.GetWorkerId(),
		BatchSize:               int(req.GetBatchSize()),
		PendingPublishMaxAge:    durationFromMillis(req.GetPendingPublishMaxAgeMs()),
		PublishingSessionMaxAge: durationFromMillis(req.GetPublishingSessionMaxAgeMs()),
	})
	if err != nil {
		return nil, xerr.ToGRPCError(err)
	}
	return &userv1.CronBatchResponse{
		ClaimedCount:   int32(result.ClosedCount),
		ProcessedCount: int32(result.ClosedCount),
		SuccessCount:   int32(result.ClosedCount),
		HasMore:        result.ClosedCount >= normalizedBatchSize(req.GetBatchSize(), 100),
	}, nil
}

// CompensateRoomOpenSessions 关闭异常长时间未收到 Left/Kicked 事件的用户在房统计 session，不反写 Room Cell。
func (s *Server) CompensateRoomOpenSessions(ctx context.Context, req *userv1.CronBatchRequest) (*userv1.CronBatchResponse, error) {
	ctx = contextWithApp(ctx, req.GetMeta())
	if s.roomTimeSvc == nil {
		return nil, xerr.ToGRPCError(xerr.New(xerr.Unavailable, "room time service is not configured"))
	}
	result, err := s.roomTimeSvc.ProcessOpenSessionCompensationBatch(ctx, roomtimeservice.CompensationWorkerOptions{
		WorkerID:          req.GetWorkerId(),
		BatchSize:         int(req.GetBatchSize()),
		OpenSessionMaxAge: durationFromMillis(req.GetOpenSessionMaxAgeMs()),
	})
	if err != nil {
		return nil, xerr.ToGRPCError(err)
	}
	return &userv1.CronBatchResponse{
		ClaimedCount:   int32(result.ClosedCount),
		ProcessedCount: int32(result.ClosedCount + result.OrphanLeavesDeleted),
		SuccessCount:   int32(result.ClosedCount),
		HasMore:        result.ClosedCount >= normalizedBatchSize(req.GetBatchSize(), 200),
	}, nil
}

// ExpireManagerUserBlocks 释放到期的经理封禁记录；真正是否恢复 users.status 由 user-service 内部封禁来源判断。
func (s *Server) ExpireManagerUserBlocks(ctx context.Context, req *userv1.CronBatchRequest) (*userv1.CronBatchResponse, error) {
	ctx = contextWithApp(ctx, req.GetMeta())
	if s.userSvc == nil {
		return nil, xerr.ToGRPCError(xerr.New(xerr.Unavailable, "user service is not configured"))
	}
	processed, err := s.userSvc.ExpireManagerUserBlocks(ctx, req.GetBatchSize())
	if err != nil {
		return nil, xerr.ToGRPCError(err)
	}
	return &userv1.CronBatchResponse{
		ClaimedCount:   int32(processed),
		ProcessedCount: int32(processed),
		SuccessCount:   int32(processed),
		HasMore:        processed >= normalizedBatchSize(req.GetBatchSize(), 100),
	}, nil
}

// ExpireAdminUserBans 释放到期的后台管理员限时封禁；永久封禁和最终恢复判断都由 user-service 持有。
func (s *Server) ExpireAdminUserBans(ctx context.Context, req *userv1.CronBatchRequest) (*userv1.CronBatchResponse, error) {
	ctx = contextWithApp(ctx, req.GetMeta())
	if s.userSvc == nil {
		return nil, xerr.ToGRPCError(xerr.New(xerr.Unavailable, "user service is not configured"))
	}
	processed, err := s.userSvc.ExpireAdminUserBans(ctx, req.GetBatchSize())
	if err != nil {
		return nil, xerr.ToGRPCError(err)
	}
	return &userv1.CronBatchResponse{
		ClaimedCount:   int32(processed),
		ProcessedCount: int32(processed),
		SuccessCount:   int32(processed),
		HasMore:        processed >= normalizedBatchSize(req.GetBatchSize(), 100),
	}, nil
}

// RefreshCPIntimacyLeaderboard 重建 CP 亲密值排行榜 Redis zset；cron-service 只负责调度和租约。
func (s *Server) RefreshCPIntimacyLeaderboard(ctx context.Context, req *userv1.CronBatchRequest) (*userv1.CronBatchResponse, error) {
	// cron-service 每个 app_code 单独触发一次；这里把 app_code 放进 context 后，MySQL 查询和 Redis key 都会按租户隔离。
	ctx = contextWithApp(ctx, req.GetMeta())
	if s.cpSvc == nil {
		return nil, xerr.ToGRPCError(xerr.New(xerr.Unavailable, "cp service is not configured"))
	}
	// batch_size 在这个任务里不是“继续翻页”的游标，只是本轮榜单最大行数；service 会全量替换 Redis 读模型。
	processed, err := s.cpSvc.RefreshIntimacyLeaderboard(ctx, int(req.GetBatchSize()))
	if err != nil {
		return nil, xerr.ToGRPCError(err)
	}
	// 刷新是一次性快照任务，没有 has_more 续跑语义；下一次 5 分钟调度会重新生成完整榜单。
	return &userv1.CronBatchResponse{
		ClaimedCount:   int32(processed),
		ProcessedCount: int32(processed),
		SuccessCount:   int32(processed),
		HasMore:        false,
	}, nil
}

// ProcessPrivateMessageHistoryBatch 从腾讯 IM 小时归档恢复 Host 私聊发送人数事实。
// 任务只在 cron 后台运行；即使腾讯归档延迟或下载失败，也不会影响客户端私聊发送。
func (s *Server) ProcessPrivateMessageHistoryBatch(ctx context.Context, req *userv1.CronBatchRequest) (*userv1.CronBatchResponse, error) {
	ctx = contextWithApp(ctx, req.GetMeta())
	if s.hostSvc == nil {
		return nil, xerr.ToGRPCError(xerr.New(xerr.Unavailable, "host service is not configured"))
	}
	result, err := s.hostSvc.ProcessPrivateMessageHistoryBatch(
		ctx,
		req.GetRunId(),
		durationFromMillis(req.GetLockTtlMs()),
		int(req.GetBatchSize()),
	)
	if err != nil {
		return nil, xerr.ToGRPCError(err)
	}
	claimed := int32(0)
	if result.Claimed {
		claimed = 1
	}
	failureCount := int32(0)
	if result.RetryPending {
		failureCount = 1
	}
	return &userv1.CronBatchResponse{
		ClaimedCount:   claimed,
		ProcessedCount: int32(result.SourceCount),
		SuccessCount:   int32(result.ImportedCount),
		FailureCount:   failureCount,
		// 小时归档有意按 cron interval 节流；回补不通过 has_more 形成无间隔下载突发。
		HasMore: false,
	}, nil
}

func durationFromMillis(value int64) time.Duration {
	if value <= 0 {
		return 0
	}
	return time.Duration(value) * time.Millisecond
}

func normalizedBatchSize(value int32, fallback int) int {
	if value <= 0 {
		return fallback
	}
	return int(value)
}
