package grpc

import (
	"context"

	activityv1 "hyapp.local/api/proto/activity/v1"
	"hyapp/pkg/appcode"
	"hyapp/pkg/xerr"
	domain "hyapp/services/activity-service/internal/domain/inviteactivity"
	service "hyapp/services/activity-service/internal/service/inviteactivity"
)

type InviteActivityRewardServer struct {
	activityv1.UnimplementedInviteActivityRewardServiceServer

	svc *service.Service
}

func NewInviteActivityRewardServer(svc *service.Service) *InviteActivityRewardServer {
	return &InviteActivityRewardServer{svc: svc}
}

func (s *InviteActivityRewardServer) GetInviteActivityRewardStatus(ctx context.Context, req *activityv1.GetInviteActivityRewardStatusRequest) (*activityv1.GetInviteActivityRewardStatusResponse, error) {
	ctx = appcode.WithContext(ctx, req.GetMeta().GetAppCode())
	status, err := s.svc.GetStatus(ctx, req.GetUserId())
	if err != nil {
		return nil, xerr.ToGRPCError(err)
	}
	return &activityv1.GetInviteActivityRewardStatusResponse{Status: inviteActivityRewardStatusToProto(status)}, nil
}

func (s *InviteActivityRewardServer) ClaimInviteActivityReward(ctx context.Context, req *activityv1.ClaimInviteActivityRewardRequest) (*activityv1.ClaimInviteActivityRewardResponse, error) {
	ctx = appcode.WithContext(ctx, req.GetMeta().GetAppCode())
	claim, status, err := s.svc.Claim(ctx, req.GetUserId(), req.GetRewardType(), req.GetTierId(), req.GetCommandId())
	if err != nil {
		return nil, xerr.ToGRPCError(err)
	}
	return &activityv1.ClaimInviteActivityRewardResponse{Claim: inviteActivityRewardClaimToProto(claim), Status: inviteActivityRewardStatusToProto(status)}, nil
}

func (s *InviteActivityRewardServer) ListInviteActivityLeaderboard(ctx context.Context, req *activityv1.ListInviteActivityLeaderboardRequest) (*activityv1.ListInviteActivityLeaderboardResponse, error) {
	ctx = appcode.WithContext(ctx, req.GetMeta().GetAppCode())
	result, err := s.svc.ListLeaderboard(ctx, domain.LeaderboardQuery{
		CycleKey: req.GetCycleKey(),
		Page:     req.GetPage(),
		PageSize: req.GetPageSize(),
	})
	if err != nil {
		return nil, xerr.ToGRPCError(err)
	}
	resp := &activityv1.ListInviteActivityLeaderboardResponse{
		Entries:      make([]*activityv1.InviteActivityLeaderboardEntry, 0, len(result.Entries)),
		Total:        result.Total,
		CycleKey:     result.CycleKey,
		ServerTimeMs: result.ServerTimeMS,
	}
	for _, entry := range result.Entries {
		resp.Entries = append(resp.Entries, inviteActivityLeaderboardEntryToProto(entry))
	}
	return resp, nil
}

func (s *InviteActivityRewardServer) ListInviteActivityInvitees(ctx context.Context, req *activityv1.ListInviteActivityInviteesRequest) (*activityv1.ListInviteActivityInviteesResponse, error) {
	ctx = appcode.WithContext(ctx, req.GetMeta().GetAppCode())
	result, err := s.svc.ListInvitees(ctx, req.GetUserId(), req.GetPage(), req.GetPageSize())
	if err != nil {
		return nil, xerr.ToGRPCError(err)
	}
	resp := &activityv1.ListInviteActivityInviteesResponse{
		Invitees:     make([]*activityv1.InviteActivityRewardInviteeSummary, 0, len(result.Invitees)),
		Total:        result.Total,
		Page:         result.Page,
		PageSize:     result.PageSize,
		CycleKey:     result.CycleKey,
		ServerTimeMs: result.ServerTimeMS,
	}
	for _, invitee := range result.Invitees {
		resp.Invitees = append(resp.Invitees, inviteActivityRewardInviteeSummaryToProto(invitee))
	}
	return resp, nil
}

type AdminInviteActivityRewardServer struct {
	activityv1.UnimplementedAdminInviteActivityRewardServiceServer

	svc *service.Service
}

func NewAdminInviteActivityRewardServer(svc *service.Service) *AdminInviteActivityRewardServer {
	return &AdminInviteActivityRewardServer{svc: svc}
}

func (s *AdminInviteActivityRewardServer) GetInviteActivityRewardConfig(ctx context.Context, req *activityv1.GetInviteActivityRewardConfigRequest) (*activityv1.GetInviteActivityRewardConfigResponse, error) {
	ctx = appcode.WithContext(ctx, req.GetMeta().GetAppCode())
	config, err := s.svc.GetConfig(ctx)
	if err != nil {
		return nil, xerr.ToGRPCError(err)
	}
	return &activityv1.GetInviteActivityRewardConfigResponse{Config: inviteActivityRewardConfigToProto(config)}, nil
}

func (s *AdminInviteActivityRewardServer) UpdateInviteActivityRewardConfig(ctx context.Context, req *activityv1.UpdateInviteActivityRewardConfigRequest) (*activityv1.UpdateInviteActivityRewardConfigResponse, error) {
	ctx = appcode.WithContext(ctx, req.GetMeta().GetAppCode())
	tiers := make([]domain.Tier, 0, len(req.GetTiers()))
	for _, tier := range req.GetTiers() {
		tiers = append(tiers, domain.Tier{
			TierID:                    tier.GetTierId(),
			RewardType:                tier.GetRewardType(),
			TierCode:                  tier.GetTierCode(),
			TierName:                  tier.GetTierName(),
			ThresholdCoinAmount:       tier.GetThresholdCoinAmount(),
			ThresholdValidInviteCount: tier.GetThresholdValidInviteCount(),
			RewardCoinAmount:          tier.GetRewardCoinAmount(),
			Status:                    tier.GetStatus(),
			SortOrder:                 tier.GetSortOrder(),
		})
	}
	config, err := s.svc.UpdateConfig(ctx, domain.Config{
		Enabled:                          req.GetEnabled(),
		PerInviteInviterRewardCoinAmount: req.GetPerInviteInviterRewardCoinAmount(),
		PerInviteInviteeRewardCoinAmount: req.GetPerInviteInviteeRewardCoinAmount(),
		Tiers:                            tiers,
		UpdatedByAdminID:                 req.GetOperatorAdminId(),
	})
	if err != nil {
		return nil, xerr.ToGRPCError(err)
	}
	return &activityv1.UpdateInviteActivityRewardConfigResponse{Config: inviteActivityRewardConfigToProto(config)}, nil
}

func (s *AdminInviteActivityRewardServer) ListInviteActivityRewardClaims(ctx context.Context, req *activityv1.ListInviteActivityRewardClaimsRequest) (*activityv1.ListInviteActivityRewardClaimsResponse, error) {
	ctx = appcode.WithContext(ctx, req.GetMeta().GetAppCode())
	claims, total, err := s.svc.ListClaims(ctx, domain.ClaimQuery{
		Status:     req.GetStatus(),
		RewardType: req.GetRewardType(),
		UserID:     req.GetUserId(),
		CycleKey:   req.GetCycleKey(),
		Page:       req.GetPage(),
		PageSize:   req.GetPageSize(),
	})
	if err != nil {
		return nil, xerr.ToGRPCError(err)
	}
	resp := &activityv1.ListInviteActivityRewardClaimsResponse{Claims: make([]*activityv1.InviteActivityRewardClaim, 0, len(claims)), Total: total}
	for _, claim := range claims {
		resp.Claims = append(resp.Claims, inviteActivityRewardClaimToProto(claim))
	}
	return resp, nil
}

func (s *AdminInviteActivityRewardServer) ListInviteActivityRewardSummaries(ctx context.Context, req *activityv1.ListInviteActivityRewardSummariesRequest) (*activityv1.ListInviteActivityRewardSummariesResponse, error) {
	ctx = appcode.WithContext(ctx, req.GetMeta().GetAppCode())
	summaries, total, err := s.svc.ListSummaries(ctx, domain.SummaryQuery{
		UserID:   req.GetUserId(),
		CycleKey: req.GetCycleKey(),
		Page:     req.GetPage(),
		PageSize: req.GetPageSize(),
	})
	if err != nil {
		return nil, xerr.ToGRPCError(err)
	}
	resp := &activityv1.ListInviteActivityRewardSummariesResponse{Summaries: make([]*activityv1.InviteActivityRewardSummary, 0, len(summaries)), Total: total}
	for _, summary := range summaries {
		resp.Summaries = append(resp.Summaries, inviteActivityRewardSummaryToProto(summary))
	}
	return resp, nil
}

func inviteActivityRewardStatusToProto(status domain.StatusResult) *activityv1.InviteActivityRewardStatus {
	resp := &activityv1.InviteActivityRewardStatus{
		Config:        inviteActivityRewardConfigToProto(status.Config),
		Progress:      inviteActivityRewardProgressToProto(status.Progress),
		Claims:        make([]*activityv1.InviteActivityRewardClaim, 0, len(status.Claims)),
		CycleKey:      status.Cycle.Key,
		PeriodStartMs: status.Cycle.StartMS,
		PeriodEndMs:   status.Cycle.EndMS,
		ServerTimeMs:  status.ServerTimeMS,
	}
	for _, claim := range status.Claims {
		resp.Claims = append(resp.Claims, inviteActivityRewardClaimToProto(claim))
	}
	return resp
}

func inviteActivityRewardConfigToProto(config domain.Config) *activityv1.InviteActivityRewardConfig {
	resp := &activityv1.InviteActivityRewardConfig{
		AppCode:                          config.AppCode,
		Enabled:                          config.Enabled,
		PerInviteInviterRewardCoinAmount: config.PerInviteInviterRewardCoinAmount,
		PerInviteInviteeRewardCoinAmount: config.PerInviteInviteeRewardCoinAmount,
		Tiers:                            make([]*activityv1.InviteActivityRewardTier, 0, len(config.Tiers)),
		UpdatedByAdminId:                 config.UpdatedByAdminID,
		CreatedAtMs:                      config.CreatedAtMS,
		UpdatedAtMs:                      config.UpdatedAtMS,
	}
	for _, tier := range config.Tiers {
		resp.Tiers = append(resp.Tiers, inviteActivityRewardTierToProto(tier))
	}
	return resp
}

func inviteActivityRewardTierToProto(tier domain.Tier) *activityv1.InviteActivityRewardTier {
	return &activityv1.InviteActivityRewardTier{
		TierId:                    tier.TierID,
		RewardType:                tier.RewardType,
		TierCode:                  tier.TierCode,
		TierName:                  tier.TierName,
		ThresholdCoinAmount:       tier.ThresholdCoinAmount,
		ThresholdValidInviteCount: tier.ThresholdValidInviteCount,
		RewardCoinAmount:          tier.RewardCoinAmount,
		Status:                    tier.Status,
		SortOrder:                 tier.SortOrder,
		CreatedAtMs:               tier.CreatedAtMS,
		UpdatedAtMs:               tier.UpdatedAtMS,
	}
}

func inviteActivityRewardProgressToProto(progress domain.Progress) *activityv1.InviteActivityRewardProgress {
	return &activityv1.InviteActivityRewardProgress{
		AppCode:                 progress.AppCode,
		CycleKey:                progress.CycleKey,
		UserId:                  progress.UserID,
		TotalRechargeCoinAmount: progress.TotalRechargeCoinAmount,
		ValidInviteCount:        progress.ValidInviteCount,
		ClaimedRewardCoinAmount: progress.ClaimedRewardCoinAmount,
		UpdatedAtMs:             progress.UpdatedAtMS,
	}
}

func inviteActivityRewardClaimToProto(claim domain.Claim) *activityv1.InviteActivityRewardClaim {
	if claim.ClaimID == "" {
		return nil
	}
	return &activityv1.InviteActivityRewardClaim{
		ClaimId:                   claim.ClaimID,
		AppCode:                   claim.AppCode,
		CycleKey:                  claim.CycleKey,
		UserId:                    claim.UserID,
		RewardType:                claim.RewardType,
		CommandId:                 claim.CommandID,
		TierId:                    claim.TierID,
		TierCode:                  claim.TierCode,
		TierName:                  claim.TierName,
		ThresholdCoinAmount:       claim.ThresholdCoinAmount,
		ThresholdValidInviteCount: claim.ThresholdValidInviteCount,
		ReachedValue:              claim.ReachedValue,
		RewardCoinAmount:          claim.RewardCoinAmount,
		Status:                    claim.Status,
		WalletCommandId:           claim.WalletCommandID,
		WalletTransactionId:       claim.WalletTransactionID,
		FailureReason:             claim.FailureReason,
		GrantedAtMs:               claim.GrantedAtMS,
		CreatedAtMs:               claim.CreatedAtMS,
		UpdatedAtMs:               claim.UpdatedAtMS,
	}
}

func inviteActivityRewardSummaryToProto(summary domain.Summary) *activityv1.InviteActivityRewardSummary {
	resp := &activityv1.InviteActivityRewardSummary{
		AppCode:                 summary.AppCode,
		CycleKey:                summary.CycleKey,
		UserId:                  summary.UserID,
		InviteCount:             summary.InviteCount,
		ValidInviteCount:        summary.ValidInviteCount,
		TotalRechargeCoinAmount: summary.TotalRechargeCoinAmount,
		TotalRewardCoinAmount:   summary.TotalRewardCoinAmount,
		UpdatedAtMs:             summary.UpdatedAtMS,
		Invitees:                make([]*activityv1.InviteActivityRewardInviteeSummary, 0, len(summary.Invitees)),
	}
	for _, invitee := range summary.Invitees {
		resp.Invitees = append(resp.Invitees, inviteActivityRewardInviteeSummaryToProto(invitee))
	}
	return resp
}

func inviteActivityRewardInviteeSummaryToProto(invitee domain.InviteeSummary) *activityv1.InviteActivityRewardInviteeSummary {
	return &activityv1.InviteActivityRewardInviteeSummary{
		InvitedUserId:      invitee.InvitedUserID,
		RechargeCoinAmount: invitee.RechargeCoinAmount,
		InvitedAtMs:        invitee.InvitedAtMS,
	}
}

func inviteActivityLeaderboardEntryToProto(entry domain.LeaderboardEntry) *activityv1.InviteActivityLeaderboardEntry {
	return &activityv1.InviteActivityLeaderboardEntry{
		RankNo:                  entry.RankNo,
		UserId:                  entry.UserID,
		ValidInviteCount:        entry.ValidInviteCount,
		TotalRechargeCoinAmount: entry.TotalRechargeCoinAmount,
	}
}
