package grpc

import (
	"context"
	"time"

	activityv1 "hyapp.local/api/proto/activity/v1"
	"hyapp/pkg/appcode"
	"hyapp/pkg/xerr"
	achievementservice "hyapp/services/activity-service/internal/service/achievement"
	activitytemplateservice "hyapp/services/activity-service/internal/service/activitytemplate"
	agencyopeningservice "hyapp/services/activity-service/internal/service/agencyopening"
	cpweeklyrankservice "hyapp/services/activity-service/internal/service/cpweeklyrank"
	growthservice "hyapp/services/activity-service/internal/service/growth"
	messageservice "hyapp/services/activity-service/internal/service/message"
	roomturnoverrewardservice "hyapp/services/activity-service/internal/service/roomturnoverreward"
	weeklystarservice "hyapp/services/activity-service/internal/service/weeklystar"
)

// CronServer exposes activity-service owned background batches to cron-service.
type CronServer struct {
	activityv1.UnimplementedActivityCronServiceServer

	message          *messageservice.Service
	growth           *growthservice.Service
	achievement      *achievementservice.Service
	weeklyStar       *weeklystarservice.Service
	roomReward       *roomturnoverrewardservice.Service
	agencyOpen       *agencyopeningservice.Service
	cpWeekly         *cpweeklyrankservice.Service
	activityTemplate *activitytemplateservice.Service
}

// NewCronServer creates the internal cron adapter without exposing message storage.
func NewCronServer(messageSvc *messageservice.Service, growthSvc *growthservice.Service, achievementSvc *achievementservice.Service, weeklyStarSvc *weeklystarservice.Service, roomRewardSvc *roomturnoverrewardservice.Service, agencyOpeningSvc *agencyopeningservice.Service, cpWeeklyRankSvc *cpweeklyrankservice.Service, activityTemplateSvc ...*activitytemplateservice.Service) *CronServer {
	server := &CronServer{message: messageSvc}
	server.growth = growthSvc
	server.achievement = achievementSvc
	server.weeklyStar = weeklyStarSvc
	server.roomReward = roomRewardSvc
	server.agencyOpen = agencyOpeningSvc
	server.cpWeekly = cpWeeklyRankSvc
	if len(activityTemplateSvc) > 0 {
		server.activityTemplate = activityTemplateSvc[0]
	}
	return server
}

// ProcessActivityTemplateSettlementBatch closes due UTC daily/total boards, freezes immutable
// snapshots and retries wallet resource-group grants with stable commands.
func (s *CronServer) ProcessActivityTemplateSettlementBatch(ctx context.Context, req *activityv1.CronBatchRequest) (*activityv1.CronBatchResponse, error) {
	ctx = appcode.WithContext(ctx, req.GetMeta().GetAppCode())
	if s.activityTemplate == nil {
		return nil, xerr.ToGRPCError(xerr.New(xerr.Unavailable, "activity template runtime service is not configured"))
	}
	claimed, processed, success, failure, hasMore, err := s.activityTemplate.ProcessRuntimeSettlementBatch(
		ctx, req.GetRunId(), req.GetWorkerId(), req.GetBatchSize(), durationFromMillis(req.GetLockTtlMs()))
	if err != nil {
		return nil, xerr.ToGRPCError(err)
	}
	return &activityv1.CronBatchResponse{ClaimedCount: claimed, ProcessedCount: processed, SuccessCount: success, FailureCount: failure, HasMore: hasMore}, nil
}

// ProcessMessageFanoutBatch claims and processes one message fanout page.
func (s *CronServer) ProcessMessageFanoutBatch(ctx context.Context, req *activityv1.CronBatchRequest) (*activityv1.CronBatchResponse, error) {
	ctx = appcode.WithContext(ctx, req.GetMeta().GetAppCode())
	if s.message == nil {
		return nil, xerr.ToGRPCError(xerr.New(xerr.Unavailable, "message service is not configured"))
	}
	processed, err := s.message.ProcessNextFanoutJob(ctx, messageservice.FanoutWorkerOptions{
		WorkerID:  req.GetWorkerId(),
		BatchSize: int(req.GetBatchSize()),
		LockTTL:   durationFromMillis(req.GetLockTtlMs()),
	})
	if err != nil {
		return nil, xerr.ToGRPCError(err)
	}
	if !processed {
		return &activityv1.CronBatchResponse{}, nil
	}
	return &activityv1.CronBatchResponse{
		ClaimedCount:   1,
		ProcessedCount: 1,
		SuccessCount:   1,
		HasMore:        true,
	}, nil
}

// ProcessLevelRewardBatch claims and grants pending permanent level rewards.
func (s *CronServer) ProcessLevelRewardBatch(ctx context.Context, req *activityv1.CronBatchRequest) (*activityv1.CronBatchResponse, error) {
	ctx = appcode.WithContext(ctx, req.GetMeta().GetAppCode())
	if s.growth == nil {
		return nil, xerr.ToGRPCError(xerr.New(xerr.Unavailable, "growth service is not configured"))
	}
	claimed, processed, success, failure, hasMore, err := s.growth.ProcessLevelRewardBatch(ctx, req.GetRunId(), req.GetWorkerId(), int(req.GetBatchSize()), durationFromMillis(req.GetLockTtlMs()))
	if err != nil {
		return nil, xerr.ToGRPCError(err)
	}
	return &activityv1.CronBatchResponse{
		ClaimedCount:   claimed,
		ProcessedCount: processed,
		SuccessCount:   success,
		FailureCount:   failure,
		HasMore:        hasMore,
	}, nil
}

// ProcessTemporaryLevelBatch advances expired overlays, sends owner-created inbox notices and revokes temporary grants.
func (s *CronServer) ProcessTemporaryLevelBatch(ctx context.Context, req *activityv1.CronBatchRequest) (*activityv1.CronBatchResponse, error) {
	ctx = appcode.WithContext(ctx, req.GetMeta().GetAppCode())
	if s.growth == nil {
		return nil, xerr.ToGRPCError(xerr.New(xerr.Unavailable, "growth service is not configured"))
	}
	claimed, processed, success, failure, hasMore, err := s.growth.ProcessTemporaryLevelBatch(ctx, req.GetWorkerId(), int(req.GetBatchSize()), durationFromMillis(req.GetLockTtlMs()))
	if err != nil {
		return nil, xerr.ToGRPCError(err)
	}
	return &activityv1.CronBatchResponse{ClaimedCount: claimed, ProcessedCount: processed, SuccessCount: success, FailureCount: failure, HasMore: hasMore}, nil
}

// ProcessAchievementRewardBatch claims and grants pending achievement badge/resource rewards.
func (s *CronServer) ProcessAchievementRewardBatch(ctx context.Context, req *activityv1.CronBatchRequest) (*activityv1.CronBatchResponse, error) {
	ctx = appcode.WithContext(ctx, req.GetMeta().GetAppCode())
	if s.achievement == nil {
		return nil, xerr.ToGRPCError(xerr.New(xerr.Unavailable, "achievement service is not configured"))
	}
	claimed, processed, success, failure, hasMore, err := s.achievement.ProcessAchievementRewardBatch(ctx, req.GetRunId(), req.GetWorkerId(), int(req.GetBatchSize()), durationFromMillis(req.GetLockTtlMs()))
	if err != nil {
		return nil, xerr.ToGRPCError(err)
	}
	return &activityv1.CronBatchResponse{
		ClaimedCount:   claimed,
		ProcessedCount: processed,
		SuccessCount:   success,
		FailureCount:   failure,
		HasMore:        hasMore,
	}, nil
}

// ProcessWeeklyStarSettlementBatch claims ended weekly-star cycles and grants Top rewards.
func (s *CronServer) ProcessWeeklyStarSettlementBatch(ctx context.Context, req *activityv1.CronBatchRequest) (*activityv1.CronBatchResponse, error) {
	ctx = appcode.WithContext(ctx, req.GetMeta().GetAppCode())
	if s.weeklyStar == nil {
		return nil, xerr.ToGRPCError(xerr.New(xerr.Unavailable, "weekly star service is not configured"))
	}
	claimed, processed, success, failure, hasMore, err := s.weeklyStar.ProcessSettlementBatch(ctx, req.GetRunId(), req.GetWorkerId(), req.GetBatchSize(), durationFromMillis(req.GetLockTtlMs()))
	if err != nil {
		return nil, xerr.ToGRPCError(err)
	}
	return &activityv1.CronBatchResponse{
		ClaimedCount:   claimed,
		ProcessedCount: processed,
		SuccessCount:   success,
		FailureCount:   failure,
		HasMore:        hasMore,
	}, nil
}

// ProcessCPWeeklyRankSettlementBatch closes the previous UTC CP week and grants configured Top rewards.
func (s *CronServer) ProcessCPWeeklyRankSettlementBatch(ctx context.Context, req *activityv1.CronBatchRequest) (*activityv1.CronBatchResponse, error) {
	ctx = appcode.WithContext(ctx, req.GetMeta().GetAppCode())
	if s.cpWeekly == nil {
		return nil, xerr.ToGRPCError(xerr.New(xerr.Unavailable, "cp weekly rank service is not configured"))
	}
	claimed, processed, success, failure, hasMore, err := s.cpWeekly.ProcessSettlementBatch(ctx, req.GetRunId(), req.GetWorkerId(), req.GetBatchSize())
	if err != nil {
		return nil, xerr.ToGRPCError(err)
	}
	return &activityv1.CronBatchResponse{
		ClaimedCount:   claimed,
		ProcessedCount: processed,
		SuccessCount:   success,
		FailureCount:   failure,
		HasMore:        hasMore,
	}, nil
}

// ProcessRoomTurnoverRewardSettlementBatch closes the previous UTC week and grants pending room rewards.
func (s *CronServer) ProcessRoomTurnoverRewardSettlementBatch(ctx context.Context, req *activityv1.CronBatchRequest) (*activityv1.CronBatchResponse, error) {
	// cron-service 只负责调度，activity-service 才拥有上一 UTC 周期认领 settlement 和发奖状态机。
	ctx = appcode.WithContext(ctx, req.GetMeta().GetAppCode())
	if s.roomReward == nil {
		return nil, xerr.ToGRPCError(xerr.New(xerr.Unavailable, "room turnover reward service is not configured"))
	}
	result, err := s.roomReward.ProcessSettlementBatch(ctx, int(req.GetBatchSize()))
	if err != nil {
		return nil, xerr.ToGRPCError(err)
	}
	return &activityv1.CronBatchResponse{
		ClaimedCount:   result.ClaimedCount,
		ProcessedCount: result.ProcessedCount,
		SuccessCount:   result.SuccessCount,
		FailureCount:   result.FailureCount,
		HasMore:        result.HasMore,
	}, nil
}

// ProcessAgencyOpeningSettlementBatch claims ended agency-opening cycles and grants leaderboard COIN rewards.
func (s *CronServer) ProcessAgencyOpeningSettlementBatch(ctx context.Context, req *activityv1.CronBatchRequest) (*activityv1.CronBatchResponse, error) {
	ctx = appcode.WithContext(ctx, req.GetMeta().GetAppCode())
	if s.agencyOpen == nil {
		return nil, xerr.ToGRPCError(xerr.New(xerr.Unavailable, "agency opening service is not configured"))
	}
	claimed, processed, success, failure, hasMore, err := s.agencyOpen.ProcessSettlementBatch(ctx, req.GetRunId(), req.GetWorkerId(), req.GetBatchSize(), durationFromMillis(req.GetLockTtlMs()))
	if err != nil {
		return nil, xerr.ToGRPCError(err)
	}
	return &activityv1.CronBatchResponse{
		ClaimedCount:   claimed,
		ProcessedCount: processed,
		SuccessCount:   success,
		FailureCount:   failure,
		HasMore:        hasMore,
	}, nil
}

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