package grpc

import (
	"context"
	"time"

	activityv1 "hyapp.local/api/proto/activity/v1"
	"hyapp/pkg/appcode"
	"hyapp/pkg/xerr"
	broadcastdomain "hyapp/services/activity-service/internal/domain/broadcast"
	activitytemplateservice "hyapp/services/activity-service/internal/service/activitytemplate"
	agencyopeningservice "hyapp/services/activity-service/internal/service/agencyopening"
	broadcastservice "hyapp/services/activity-service/internal/service/broadcast"
	growthservice "hyapp/services/activity-service/internal/service/growth"
	roomturnoverrewardservice "hyapp/services/activity-service/internal/service/roomturnoverreward"
	taskservice "hyapp/services/activity-service/internal/service/task"
	weeklystarservice "hyapp/services/activity-service/internal/service/weeklystar"
)

// BroadcastServer 把 IM 播报能力暴露为内部 gRPC。
// 外部 HTTP 不直接调用这里；gateway/cron/room-service 只能通过 protobuf 契约进入 activity-service。
type BroadcastServer struct {
	activityv1.UnimplementedBroadcastServiceServer
	activityv1.UnimplementedRoomEventConsumerServiceServer
	activityv1.UnimplementedAdminBroadcastServiceServer

	svc              *broadcastservice.Service
	growth           *growthservice.Service
	task             *taskservice.Service
	roomReward       *roomturnoverrewardservice.Service
	weeklyStar       *weeklystarservice.Service
	agencyOpen       *agencyopeningservice.Service
	activityTemplate *activitytemplateservice.Service
}

func (s *BroadcastServer) GetGameWinBroadcastConfig(ctx context.Context, req *activityv1.GetGameWinBroadcastConfigRequest) (*activityv1.GetGameWinBroadcastConfigResponse, error) {
	ctx = appcode.WithContext(ctx, req.GetMeta().GetAppCode())
	config, err := s.svc.GetGameWinBroadcastConfig(ctx)
	if err != nil {
		return nil, xerr.ToGRPCError(err)
	}
	return &activityv1.GetGameWinBroadcastConfigResponse{Config: gameWinBroadcastConfigProto(config)}, nil
}

func (s *BroadcastServer) UpdateGameWinBroadcastConfig(ctx context.Context, req *activityv1.UpdateGameWinBroadcastConfigRequest) (*activityv1.UpdateGameWinBroadcastConfigResponse, error) {
	ctx = appcode.WithContext(ctx, req.GetMeta().GetAppCode())
	stored, err := s.svc.UpdateGameWinBroadcastConfig(ctx, req.GetEnabled(), req.GetMinWinCoins(), req.GetOperatorAdminId())
	if err != nil {
		return nil, xerr.ToGRPCError(err)
	}
	return &activityv1.UpdateGameWinBroadcastConfigResponse{Config: gameWinBroadcastConfigProto(stored)}, nil
}

func gameWinBroadcastConfigProto(config broadcastdomain.GameWinConfig) *activityv1.GameWinBroadcastConfig {
	return &activityv1.GameWinBroadcastConfig{
		Enabled:          config.Enabled,
		MinWinCoins:      config.MinWinCoins,
		UpdatedByAdminId: config.UpdatedByAdminID,
		CreatedAtMs:      config.CreatedAtMS,
		UpdatedAtMs:      config.UpdatedAtMS,
	}
}

// NewBroadcastServer 创建播报和 room event 消费共用的 gRPC adapter。
// room-service 的 outbox 事件只投递一次，但 activity-service 内部有多条独立投影：
// 播报负责 IM 展示，growth 负责等级进度，weekly-star 负责指定礼物榜单，roomReward 负责房间流水。
// 这里把这些模块都挂到同一个 gRPC 入口，保证本地直连投递和 MQ 投递使用同一组业务副作用。
func NewBroadcastServer(svc *broadcastservice.Service, growthSvc *growthservice.Service, weeklyStarSvc *weeklystarservice.Service, taskSvc *taskservice.Service, roomRewardSvc *roomturnoverrewardservice.Service, agencyOpeningSvc *agencyopeningservice.Service, activityTemplateSvc ...*activitytemplateservice.Service) *BroadcastServer {
	server := &BroadcastServer{svc: svc}
	server.growth = growthSvc
	server.weeklyStar = weeklyStarSvc
	server.task = taskSvc
	server.roomReward = roomRewardSvc
	server.agencyOpen = agencyOpeningSvc
	if len(activityTemplateSvc) > 0 {
		server.activityTemplate = activityTemplateSvc[0]
	}
	return server
}

func (s *BroadcastServer) EnsureBroadcastGroups(ctx context.Context, req *activityv1.EnsureBroadcastGroupsRequest) (*activityv1.EnsureBroadcastGroupsResponse, error) {
	// app_code 只从 RequestMeta 进入 context，底层 repository 才能自动带上租户隔离条件。
	ctx = appcode.WithContext(ctx, req.GetMeta().GetAppCode())
	count, err := s.svc.EnsureBroadcastGroups(ctx)
	if err != nil {
		return nil, xerr.ToGRPCError(err)
	}
	return &activityv1.EnsureBroadcastGroupsResponse{EnsuredCount: int32(count)}, nil
}

func (s *BroadcastServer) PublishRegionBroadcast(ctx context.Context, req *activityv1.PublishRegionBroadcastRequest) (*activityv1.PublishBroadcastResponse, error) {
	// 区域播报是红包、贵重礼物等高频入口，gRPC 层只做字段搬运，策略留给 service。
	ctx = appcode.WithContext(ctx, req.GetMeta().GetAppCode())
	result, err := s.svc.PublishRegionBroadcast(ctx, broadcastservice.PublishInput{
		EventID:       req.GetEventId(),
		BroadcastType: req.GetBroadcastType(),
		RegionID:      req.GetRegionId(),
		PayloadJSON:   req.GetPayloadJson(),
	})
	if err != nil {
		return nil, xerr.ToGRPCError(err)
	}
	return &activityv1.PublishBroadcastResponse{EventId: result.EventID, GroupId: result.GroupID, Status: result.Status, Created: result.Created}, nil
}

func (s *BroadcastServer) PublishGlobalBroadcast(ctx context.Context, req *activityv1.PublishGlobalBroadcastRequest) (*activityv1.PublishBroadcastResponse, error) {
	// 全局播报和区域播报共用返回结构，调用方通过 group_id/status/created 判断幂等结果。
	ctx = appcode.WithContext(ctx, req.GetMeta().GetAppCode())
	result, err := s.svc.PublishGlobalBroadcast(ctx, broadcastservice.PublishInput{
		EventID:       req.GetEventId(),
		BroadcastType: req.GetBroadcastType(),
		PayloadJSON:   req.GetPayloadJson(),
	})
	if err != nil {
		return nil, xerr.ToGRPCError(err)
	}
	return &activityv1.PublishBroadcastResponse{EventId: result.EventID, GroupId: result.GroupID, Status: result.Status, Created: result.Created}, nil
}

func (s *BroadcastServer) RemoveRegionBroadcastMember(ctx context.Context, req *activityv1.RemoveRegionBroadcastMemberRequest) (*activityv1.RemoveRegionBroadcastMemberResponse, error) {
	// 这里只删除 user_id 在旧区域群的成员关系，不删除区域群；群生命周期仍由 reconciler 管理。
	ctx = appcode.WithContext(ctx, req.GetMeta().GetAppCode())
	groupID, removed, err := s.svc.RemoveRegionBroadcastMember(ctx, req.GetUserId(), req.GetRegionId())
	if err != nil {
		return nil, xerr.ToGRPCError(err)
	}
	return &activityv1.RemoveRegionBroadcastMemberResponse{GroupId: groupID, Removed: removed}, nil
}

func (s *BroadcastServer) ProcessBroadcastOutboxBatch(ctx context.Context, req *activityv1.CronBatchRequest) (*activityv1.ProcessBroadcastOutboxBatchResponse, error) {
	// cron-service 可以显式触发一批投递；常驻 worker 也复用同一个 service 方法。
	ctx = appcode.WithContext(ctx, req.GetMeta().GetAppCode())
	result, err := s.svc.ProcessPendingBroadcasts(ctx, broadcastservice.WorkerOptions{
		WorkerID:  req.GetWorkerId(),
		BatchSize: int(req.GetBatchSize()),
		LockTTL:   time.Duration(req.GetLockTtlMs()) * time.Millisecond,
	})
	if err != nil {
		return nil, xerr.ToGRPCError(err)
	}
	return &activityv1.ProcessBroadcastOutboxBatchResponse{
		ClaimedCount: int32(result.ClaimedCount),
		SuccessCount: int32(result.SuccessCount),
		FailureCount: int32(result.FailureCount),
		HasMore:      result.HasMore,
	}, nil
}

func (s *BroadcastServer) ConsumeRoomEvent(ctx context.Context, req *activityv1.ConsumeRoomEventRequest) (*activityv1.ConsumeRoomEventResponse, error) {
	// room-service 只推送已提交的 outbox envelope；每个下游模块都以同一个 event_id 做自己的幂等表。
	// 调用顺序保持和 MQ consumer 一致：先处理可见播报，再处理用户成长，再处理运营活动统计。
	// 任一模块返回错误时直接失败，让 room-service/MQ 重试同一个 envelope，而不是吞掉部分副作用。
	ctx = appcode.WithContext(ctx, req.GetMeta().GetAppCode())
	result, err := s.svc.HandleRoomEvent(ctx, req.GetEnvelope())
	if err != nil {
		return nil, xerr.ToGRPCError(err)
	}
	if s.growth != nil {
		if _, err := s.growth.HandleRoomEvent(ctx, req.GetEnvelope()); err != nil {
			return nil, xerr.ToGRPCError(err)
		}
	}
	if s.weeklyStar != nil {
		if _, err := s.weeklyStar.HandleRoomEvent(ctx, req.GetEnvelope()); err != nil {
			return nil, xerr.ToGRPCError(err)
		}
	}
	if s.activityTemplate != nil {
		if _, err := s.activityTemplate.HandleRoomEvent(ctx, req.GetEnvelope()); err != nil {
			return nil, xerr.ToGRPCError(err)
		}
	}
	if s.agencyOpen != nil {
		if _, err := s.agencyOpen.HandleRoomEvent(ctx, req.GetEnvelope()); err != nil {
			return nil, xerr.ToGRPCError(err)
		}
	}
	if s.task != nil {
		if _, err := s.task.HandleRoomEvent(ctx, req.GetEnvelope()); err != nil {
			return nil, xerr.ToGRPCError(err)
		}
	}
	if s.roomReward != nil {
		if _, err := s.roomReward.HandleRoomEvent(ctx, req.GetEnvelope()); err != nil {
			return nil, xerr.ToGRPCError(err)
		}
	}
	return &activityv1.ConsumeRoomEventResponse{
		EventId:          result.EventID,
		Status:           result.Status,
		BroadcastEventId: result.BroadcastEventID,
		BroadcastCreated: result.BroadcastCreated,
	}, nil
}
