package grpc

import (
	"context"

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

type AgencyOpeningServer struct {
	activityv1.UnimplementedAgencyOpeningServiceServer
	svc *service.Service
}

func NewAgencyOpeningServer(svc *service.Service) *AgencyOpeningServer {
	return &AgencyOpeningServer{svc: svc}
}

func (s *AgencyOpeningServer) GetAgencyOpeningStatus(ctx context.Context, req *activityv1.GetAgencyOpeningStatusRequest) (*activityv1.GetAgencyOpeningStatusResponse, 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.GetAgencyOpeningStatusResponse{
		Cycle:            agencyOpeningCycleToProto(status.Cycle),
		Application:      agencyOpeningApplicationToProto(status.Application),
		Enabled:          status.Enabled,
		Eligible:         status.Eligible,
		Joined:           status.Joined,
		IneligibleReason: status.IneligibleReason,
		ServerTimeMs:     status.ServerTimeMS,
		Agency:           agencyOpeningAgencyToProto(status.Agency),
	}, nil
}

func (s *AgencyOpeningServer) ApplyAgencyOpening(ctx context.Context, req *activityv1.ApplyAgencyOpeningRequest) (*activityv1.ApplyAgencyOpeningResponse, error) {
	ctx = appcode.WithContext(ctx, req.GetMeta().GetAppCode())
	application, created, err := s.svc.Apply(ctx, req.GetUserId(), req.GetCommandId())
	if err != nil {
		return nil, xerr.ToGRPCError(err)
	}
	return &activityv1.ApplyAgencyOpeningResponse{Application: agencyOpeningApplicationToProto(application), Created: created}, nil
}

func (s *AgencyOpeningServer) ListAgencyOpeningLeaderboard(ctx context.Context, req *activityv1.ListAgencyOpeningApplicationsRequest) (*activityv1.ListAgencyOpeningApplicationsResponse, error) {
	ctx = appcode.WithContext(ctx, req.GetMeta().GetAppCode())
	items, total, err := s.svc.ListApplications(ctx, agencyOpeningApplicationQueryFromProto(req))
	if err != nil {
		return nil, xerr.ToGRPCError(err)
	}
	return agencyOpeningApplicationsResponse(items, total), nil
}

type AdminAgencyOpeningServer struct {
	activityv1.UnimplementedAdminAgencyOpeningServiceServer
	svc *service.Service
}

func NewAdminAgencyOpeningServer(svc *service.Service) *AdminAgencyOpeningServer {
	return &AdminAgencyOpeningServer{svc: svc}
}

func (s *AdminAgencyOpeningServer) ListAgencyOpeningCycles(ctx context.Context, req *activityv1.ListAgencyOpeningCyclesRequest) (*activityv1.ListAgencyOpeningCyclesResponse, error) {
	ctx = appcode.WithContext(ctx, req.GetMeta().GetAppCode())
	cycles, total, err := s.svc.ListCycles(ctx, domain.ListQuery{RegionID: req.GetRegionId(), Status: req.GetStatus(), StartMS: req.GetStartMs(), EndMS: req.GetEndMs(), Page: req.GetPage(), PageSize: req.GetPageSize()})
	if err != nil {
		return nil, xerr.ToGRPCError(err)
	}
	resp := &activityv1.ListAgencyOpeningCyclesResponse{Cycles: make([]*activityv1.AgencyOpeningCycle, 0, len(cycles)), Total: total}
	for _, cycle := range cycles {
		resp.Cycles = append(resp.Cycles, agencyOpeningCycleToProto(cycle))
	}
	return resp, nil
}

func (s *AdminAgencyOpeningServer) CreateAgencyOpeningCycle(ctx context.Context, req *activityv1.UpsertAgencyOpeningCycleRequest) (*activityv1.UpsertAgencyOpeningCycleResponse, error) {
	ctx = appcode.WithContext(ctx, req.GetMeta().GetAppCode())
	cycle, created, err := s.svc.CreateCycle(ctx, domain.CycleCommand{Cycle: agencyOpeningCycleFromProto(req.GetCycle()), OperatorAdminID: req.GetOperatorAdminId()})
	if err != nil {
		return nil, xerr.ToGRPCError(err)
	}
	return &activityv1.UpsertAgencyOpeningCycleResponse{Cycle: agencyOpeningCycleToProto(cycle), Created: created}, nil
}

func (s *AdminAgencyOpeningServer) GetAgencyOpeningCycle(ctx context.Context, req *activityv1.GetAgencyOpeningCycleRequest) (*activityv1.GetAgencyOpeningCycleResponse, error) {
	ctx = appcode.WithContext(ctx, req.GetMeta().GetAppCode())
	cycle, err := s.svc.GetCycle(ctx, req.GetCycleId())
	if err != nil {
		return nil, xerr.ToGRPCError(err)
	}
	return &activityv1.GetAgencyOpeningCycleResponse{Cycle: agencyOpeningCycleToProto(cycle)}, nil
}

func (s *AdminAgencyOpeningServer) UpdateAgencyOpeningCycle(ctx context.Context, req *activityv1.UpsertAgencyOpeningCycleRequest) (*activityv1.UpsertAgencyOpeningCycleResponse, error) {
	ctx = appcode.WithContext(ctx, req.GetMeta().GetAppCode())
	cycle, created, err := s.svc.UpdateCycle(ctx, domain.CycleCommand{Cycle: agencyOpeningCycleFromProto(req.GetCycle()), OperatorAdminID: req.GetOperatorAdminId()})
	if err != nil {
		return nil, xerr.ToGRPCError(err)
	}
	return &activityv1.UpsertAgencyOpeningCycleResponse{Cycle: agencyOpeningCycleToProto(cycle), Created: created}, nil
}

func (s *AdminAgencyOpeningServer) SetAgencyOpeningCycleStatus(ctx context.Context, req *activityv1.SetAgencyOpeningCycleStatusRequest) (*activityv1.SetAgencyOpeningCycleStatusResponse, error) {
	ctx = appcode.WithContext(ctx, req.GetMeta().GetAppCode())
	cycle, err := s.svc.SetCycleStatus(ctx, req.GetCycleId(), req.GetStatus(), req.GetOperatorAdminId())
	if err != nil {
		return nil, xerr.ToGRPCError(err)
	}
	return &activityv1.SetAgencyOpeningCycleStatusResponse{Cycle: agencyOpeningCycleToProto(cycle)}, nil
}

func (s *AdminAgencyOpeningServer) ListAgencyOpeningApplications(ctx context.Context, req *activityv1.ListAgencyOpeningApplicationsRequest) (*activityv1.ListAgencyOpeningApplicationsResponse, error) {
	ctx = appcode.WithContext(ctx, req.GetMeta().GetAppCode())
	items, total, err := s.svc.ListApplications(ctx, agencyOpeningApplicationQueryFromProto(req))
	if err != nil {
		return nil, xerr.ToGRPCError(err)
	}
	return agencyOpeningApplicationsResponse(items, total), nil
}

func (s *AdminAgencyOpeningServer) ReviewAgencyOpeningApplication(ctx context.Context, req *activityv1.ReviewAgencyOpeningApplicationRequest) (*activityv1.ReviewAgencyOpeningApplicationResponse, error) {
	ctx = appcode.WithContext(ctx, req.GetMeta().GetAppCode())
	if req.GetAction() != "approve" {
		return nil, xerr.ToGRPCError(xerr.New(xerr.InvalidArgument, "review action is invalid"))
	}
	item, err := s.svc.ApproveApplication(ctx, req.GetApplicationId(), req.GetOperatorAdminId())
	if err != nil {
		return nil, xerr.ToGRPCError(err)
	}
	return &activityv1.ReviewAgencyOpeningApplicationResponse{Application: agencyOpeningApplicationToProto(item)}, nil
}

func agencyOpeningCycleFromProto(item *activityv1.AgencyOpeningCycle) domain.Cycle {
	if item == nil {
		return domain.Cycle{}
	}
	cycle := domain.Cycle{
		CycleID:          item.GetCycleId(),
		RegionID:         item.GetRegionId(),
		ActivityCode:     item.GetActivityCode(),
		Title:            item.GetTitle(),
		Status:           item.GetStatus(),
		StartMS:          item.GetStartMs(),
		EndMS:            item.GetEndMs(),
		MinHostCount:     item.GetMinHostCount(),
		MaxAgencyAgeDays: item.GetMaxAgencyAgeDays(),
		CreatedByAdminID: item.GetCreatedByAdminId(),
		UpdatedByAdminID: item.GetUpdatedByAdminId(),
	}
	for _, reward := range item.GetRewards() {
		cycle.Rewards = append(cycle.Rewards, domain.Reward{
			RankNo:             reward.GetRankNo(),
			ThresholdCoinSpent: reward.GetThresholdCoinSpent(),
			RewardCoinAmount:   reward.GetRewardCoinAmount(),
		})
	}
	return cycle
}

func agencyOpeningCycleToProto(item domain.Cycle) *activityv1.AgencyOpeningCycle {
	if item.CycleID == "" {
		return nil
	}
	resp := &activityv1.AgencyOpeningCycle{
		AppCode:          item.AppCode,
		RegionId:         item.RegionID,
		CycleId:          item.CycleID,
		ActivityCode:     item.ActivityCode,
		Title:            item.Title,
		Status:           item.Status,
		StartMs:          item.StartMS,
		EndMs:            item.EndMS,
		MinHostCount:     item.MinHostCount,
		MaxAgencyAgeDays: item.MaxAgencyAgeDays,
		CreatedByAdminId: item.CreatedByAdminID,
		UpdatedByAdminId: item.UpdatedByAdminID,
		SettledAtMs:      item.SettledAtMS,
		CreatedAtMs:      item.CreatedAtMS,
		UpdatedAtMs:      item.UpdatedAtMS,
		Rewards:          make([]*activityv1.AgencyOpeningReward, 0, len(item.Rewards)),
	}
	for _, reward := range item.Rewards {
		resp.Rewards = append(resp.Rewards, &activityv1.AgencyOpeningReward{
			RankNo:             reward.RankNo,
			ThresholdCoinSpent: reward.ThresholdCoinSpent,
			RewardCoinAmount:   reward.RewardCoinAmount,
		})
	}
	return resp
}

func agencyOpeningApplicationToProto(item domain.Application) *activityv1.AgencyOpeningApplication {
	if item.ApplicationID == "" {
		return nil
	}
	return &activityv1.AgencyOpeningApplication{
		ApplicationId:            item.ApplicationID,
		RegionId:                 item.RegionID,
		CycleId:                  item.CycleID,
		AgencyId:                 item.AgencyID,
		AgencyOwnerUserId:        item.AgencyOwnerUserID,
		AgencyName:               item.AgencyName,
		HostCount:                item.HostCount,
		AgencyCreatedAtMs:        item.AgencyCreatedAtMS,
		Status:                   item.Status,
		ScoreCoinAmount:          item.ScoreCoinAmount,
		RewardRankNo:             item.RewardRankNo,
		RewardThresholdCoinSpent: item.RewardThresholdCoin,
		RewardCoinAmount:         item.RewardCoinAmount,
		WalletCommandId:          item.WalletCommandID,
		WalletTransactionId:      item.WalletTransactionID,
		FailureReason:            item.FailureReason,
		AppliedAtMs:              item.AppliedAtMS,
		ApprovedAtMs:             item.ApprovedAtMS,
		ScoreStartMs:             item.ScoreStartMS,
		ScoreEndMs:               item.ScoreEndMS,
		ReviewedByAdminId:        item.ReviewedByAdminID,
		GrantedAtMs:              item.GrantedAtMS,
		UpdatedAtMs:              item.UpdatedAtMS,
	}
}

func agencyOpeningAgencyToProto(item domain.AgencySnapshot) *activityv1.AgencyOpeningAgencySnapshot {
	if item.AgencyID <= 0 {
		return nil
	}
	return &activityv1.AgencyOpeningAgencySnapshot{
		AgencyId:          item.AgencyID,
		RegionId:          item.RegionID,
		OwnerUserId:       item.OwnerUserID,
		Name:              item.Name,
		Status:            item.Status,
		HostCount:         item.HostCount,
		AgencyCreatedAtMs: item.AgencyCreatedAtMS,
	}
}

func agencyOpeningApplicationsResponse(items []domain.Application, total int64) *activityv1.ListAgencyOpeningApplicationsResponse {
	resp := &activityv1.ListAgencyOpeningApplicationsResponse{Applications: make([]*activityv1.AgencyOpeningApplication, 0, len(items)), Total: total}
	for _, item := range items {
		resp.Applications = append(resp.Applications, agencyOpeningApplicationToProto(item))
	}
	return resp
}

func agencyOpeningApplicationQueryFromProto(req *activityv1.ListAgencyOpeningApplicationsRequest) domain.ApplicationQuery {
	return domain.ApplicationQuery{
		CycleID:           req.GetCycleId(),
		RegionID:          req.GetRegionId(),
		Status:            req.GetStatus(),
		AgencyID:          req.GetAgencyId(),
		AgencyOwnerUserID: req.GetAgencyOwnerUserId(),
		Page:              req.GetPage(),
		PageSize:          req.GetPageSize(),
	}
}
