package grpc

import (
	"context"
	"time"

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

// AchievementServer adapts achievement and badge display use cases to gRPC.
type AchievementServer struct {
	activityv1.UnimplementedAchievementServiceServer

	svc *service.Service
}

func NewAchievementServer(svc *service.Service) *AchievementServer {
	return &AchievementServer{svc: svc}
}

func (s *AchievementServer) ListAchievements(ctx context.Context, req *activityv1.ListAchievementsRequest) (*activityv1.ListAchievementsResponse, error) {
	ctx = appcode.WithContext(ctx, req.GetMeta().GetAppCode())
	items, total, err := s.svc.ListAchievements(ctx, domain.ListQuery{
		UserID:         req.GetUserId(),
		CollectionType: req.GetCollectionType(),
		CollectionID:   req.GetCollectionId(),
		Page:           req.GetPage(),
		PageSize:       req.GetPageSize(),
	})
	if err != nil {
		return nil, xerr.ToGRPCError(err)
	}
	resp := &activityv1.ListAchievementsResponse{Achievements: make([]*activityv1.UserAchievement, 0, len(items)), Total: total, ServerTimeMs: time.Now().UnixMilli()}
	for _, item := range items {
		resp.Achievements = append(resp.Achievements, userAchievementToProto(item))
	}
	return resp, nil
}

func (s *AchievementServer) ConsumeAchievementEvent(ctx context.Context, req *activityv1.ConsumeAchievementEventRequest) (*activityv1.ConsumeAchievementEventResponse, error) {
	ctx = appcode.WithContext(ctx, req.GetMeta().GetAppCode())
	result, err := s.svc.ConsumeAchievementEvent(ctx, domain.Event{
		EventID:        req.GetEventId(),
		EventType:      req.GetEventType(),
		SourceService:  req.GetSourceService(),
		UserID:         req.GetUserId(),
		MetricType:     req.GetMetricType(),
		Value:          req.GetValue(),
		OccurredAtMS:   req.GetOccurredAtMs(),
		DimensionsJSON: req.GetDimensionsJson(),
	})
	if err != nil {
		return nil, xerr.ToGRPCError(err)
	}
	return &activityv1.ConsumeAchievementEventResponse{
		EventId:                  result.EventID,
		Status:                   result.Status,
		MatchedAchievementCount:  result.MatchedAchievementCount,
		UnlockedAchievementCount: result.UnlockedAchievementCount,
		RewardJobCount:           result.RewardJobCount,
	}, nil
}

func (s *AchievementServer) ListMyBadges(ctx context.Context, req *activityv1.ListMyBadgesRequest) (*activityv1.ListMyBadgesResponse, error) {
	ctx = appcode.WithContext(ctx, req.GetMeta().GetAppCode())
	profile, err := s.svc.ListBadgeProfile(ctx, req.GetUserId())
	if err != nil {
		return nil, xerr.ToGRPCError(err)
	}
	return badgeProfileToProto(profile), nil
}

func (s *AchievementServer) SetBadgeDisplay(ctx context.Context, req *activityv1.SetBadgeDisplayRequest) (*activityv1.SetBadgeDisplayResponse, error) {
	ctx = appcode.WithContext(ctx, req.GetMeta().GetAppCode())
	items := make([]domain.BadgeDisplayItem, 0, len(req.GetItems()))
	for _, item := range req.GetItems() {
		items = append(items, badgeDisplayItemFromProto(item))
	}
	profile, err := s.svc.SetBadgeDisplay(ctx, domain.DisplayCommand{
		UserID:    req.GetUserId(),
		Slot:      req.GetSlot(),
		Items:     items,
		CommandID: req.GetCommandId(),
	})
	if err != nil {
		return nil, xerr.ToGRPCError(err)
	}
	return &activityv1.SetBadgeDisplayResponse{Profile: badgeProfileToProto(profile)}, nil
}

// AdminAchievementServer adapts admin achievement configuration commands to gRPC.
type AdminAchievementServer struct {
	activityv1.UnimplementedAdminAchievementServiceServer

	svc *service.Service
}

func NewAdminAchievementServer(svc *service.Service) *AdminAchievementServer {
	return &AdminAchievementServer{svc: svc}
}

func (s *AdminAchievementServer) ListAchievementDefinitions(ctx context.Context, req *activityv1.ListAchievementsRequest) (*activityv1.ListAchievementsResponse, error) {
	ctx = appcode.WithContext(ctx, req.GetMeta().GetAppCode())
	items, total, err := s.svc.ListAchievementDefinitions(ctx, domain.ListQuery{
		CollectionType: req.GetCollectionType(),
		CollectionID:   req.GetCollectionId(),
		Page:           req.GetPage(),
		PageSize:       req.GetPageSize(),
	})
	if err != nil {
		return nil, xerr.ToGRPCError(err)
	}
	resp := &activityv1.ListAchievementsResponse{Achievements: make([]*activityv1.UserAchievement, 0, len(items)), Total: total, ServerTimeMs: time.Now().UnixMilli()}
	for _, item := range items {
		resp.Achievements = append(resp.Achievements, &activityv1.UserAchievement{Definition: achievementDefinitionToProto(item)})
	}
	return resp, nil
}

func (s *AdminAchievementServer) UpsertAchievementDefinition(ctx context.Context, req *activityv1.UpsertAchievementDefinitionRequest) (*activityv1.UpsertAchievementDefinitionResponse, error) {
	ctx = appcode.WithContext(ctx, req.GetMeta().GetAppCode())
	conditions := make([]domain.ConditionCommand, 0, len(req.GetConditions()))
	for _, condition := range req.GetConditions() {
		conditions = append(conditions, domain.ConditionCommand{
			MetricType:          condition.GetMetricType(),
			TargetValue:         condition.GetTargetValue(),
			TargetUnit:          condition.GetTargetUnit(),
			DimensionFilterJSON: condition.GetDimensionFilterJson(),
		})
	}
	item, created, err := s.svc.UpsertAchievementDefinition(ctx, domain.DefinitionCommand{
		AchievementID:          req.GetAchievementId(),
		CollectionID:           req.GetCollectionId(),
		CollectionType:         req.GetCollectionType(),
		AchievementType:        req.GetAchievementType(),
		Title:                  req.GetTitle(),
		Description:            req.GetDescription(),
		Status:                 req.GetStatus(),
		PrimaryBadgeResourceID: req.GetPrimaryBadgeResourceId(),
		RewardResourceGroupID:  req.GetRewardResourceGroupId(),
		AutoPinPolicy:          req.GetAutoPinPolicy(),
		HonorVisibility:        req.GetHonorVisibility(),
		EffectiveFromMS:        req.GetEffectiveFromMs(),
		EffectiveToMS:          req.GetEffectiveToMs(),
		SortOrder:              req.GetSortOrder(),
		DisplayConfigJSON:      req.GetDisplayConfigJson(),
		OperatorAdminID:        req.GetOperatorAdminId(),
		Conditions:             conditions,
	})
	if err != nil {
		return nil, xerr.ToGRPCError(err)
	}
	return &activityv1.UpsertAchievementDefinitionResponse{Achievement: achievementDefinitionToProto(item), Created: created}, nil
}

func (s *AdminAchievementServer) DeleteAchievementDefinition(ctx context.Context, req *activityv1.DeleteAchievementDefinitionRequest) (*activityv1.DeleteAchievementDefinitionResponse, error) {
	ctx = appcode.WithContext(ctx, req.GetMeta().GetAppCode())
	item, err := s.svc.DeleteAchievementDefinition(ctx, req.GetAchievementId(), req.GetOperatorAdminId())
	if err != nil {
		return nil, xerr.ToGRPCError(err)
	}
	return &activityv1.DeleteAchievementDefinitionResponse{Achievement: achievementDefinitionToProto(item)}, nil
}

func userAchievementToProto(item domain.UserAchievement) *activityv1.UserAchievement {
	return &activityv1.UserAchievement{
		Definition:    achievementDefinitionToProto(item.Definition),
		CycleKey:      item.CycleKey,
		ProgressValue: item.ProgressValue,
		TargetValue:   item.TargetValue,
		UserStatus:    item.UserStatus,
		UnlockedAtMs:  item.UnlockedAtMS,
		RewardStatus:  item.RewardStatus,
	}
}

func achievementDefinitionToProto(item domain.Definition) *activityv1.AchievementDefinition {
	resp := &activityv1.AchievementDefinition{
		AchievementId:          item.AchievementID,
		CollectionId:           item.CollectionID,
		CollectionType:         item.CollectionType,
		AchievementType:        item.AchievementType,
		Title:                  item.Title,
		Description:            item.Description,
		Status:                 item.Status,
		Version:                item.Version,
		PrimaryBadgeResourceId: item.PrimaryBadgeResourceID,
		RewardResourceGroupId:  item.RewardResourceGroupID,
		AutoPinPolicy:          item.AutoPinPolicy,
		HonorVisibility:        item.HonorVisibility,
		EffectiveFromMs:        item.EffectiveFromMS,
		EffectiveToMs:          item.EffectiveToMS,
		SortOrder:              item.SortOrder,
		DisplayConfigJson:      item.DisplayConfigJSON,
		CreatedByAdminId:       item.CreatedByAdminID,
		UpdatedByAdminId:       item.UpdatedByAdminID,
		CreatedAtMs:            item.CreatedAtMS,
		UpdatedAtMs:            item.UpdatedAtMS,
		Conditions:             make([]*activityv1.AchievementCondition, 0, len(item.Conditions)),
	}
	for _, condition := range item.Conditions {
		resp.Conditions = append(resp.Conditions, achievementConditionToProto(condition))
	}
	return resp
}

func achievementConditionToProto(item domain.Condition) *activityv1.AchievementCondition {
	return &activityv1.AchievementCondition{
		ConditionId:         item.ConditionID,
		AchievementId:       item.AchievementID,
		Version:             item.Version,
		MetricType:          item.MetricType,
		TargetValue:         item.TargetValue,
		TargetUnit:          item.TargetUnit,
		DimensionFilterJson: item.DimensionFilterJSON,
	}
}

func badgeProfileToProto(profile domain.BadgeProfile) *activityv1.ListMyBadgesResponse {
	resp := &activityv1.ListMyBadgesResponse{
		StripBadges:       make([]*activityv1.BadgeDisplayItem, 0, len(profile.StripBadges)),
		ProfileTileBadges: make([]*activityv1.BadgeDisplayItem, 0, len(profile.ProfileTileBadges)),
		HonorBadges:       make([]*activityv1.BadgeDisplayItem, 0, len(profile.HonorBadges)),
		ServerTimeMs:      profile.ServerTimeMS,
	}
	for _, item := range profile.StripBadges {
		resp.StripBadges = append(resp.StripBadges, badgeDisplayItemToProto(item))
	}
	for _, item := range profile.ProfileTileBadges {
		resp.ProfileTileBadges = append(resp.ProfileTileBadges, badgeDisplayItemToProto(item))
	}
	for _, item := range profile.HonorBadges {
		resp.HonorBadges = append(resp.HonorBadges, badgeDisplayItemToProto(item))
	}
	return resp
}

func badgeDisplayItemToProto(item domain.BadgeDisplayItem) *activityv1.BadgeDisplayItem {
	return &activityv1.BadgeDisplayItem{
		Slot:          item.Slot,
		Position:      item.Position,
		BadgeForm:     item.BadgeForm,
		ResourceId:    item.ResourceID,
		EntitlementId: item.EntitlementID,
		SourceType:    item.SourceType,
		SourceId:      item.SourceID,
		PinMode:       item.PinMode,
		UpdatedAtMs:   item.UpdatedAtMS,
	}
}

func badgeDisplayItemFromProto(item *activityv1.BadgeDisplayItem) domain.BadgeDisplayItem {
	return domain.BadgeDisplayItem{
		Slot:          item.GetSlot(),
		Position:      item.GetPosition(),
		BadgeForm:     item.GetBadgeForm(),
		ResourceID:    item.GetResourceId(),
		EntitlementID: item.GetEntitlementId(),
		SourceType:    item.GetSourceType(),
		SourceID:      item.GetSourceId(),
		PinMode:       item.GetPinMode(),
		UpdatedAtMS:   item.GetUpdatedAtMs(),
	}
}
