package grpc

import (
	"context"
	"time"

	walletv1 "hyapp.local/api/proto/wallet/v1"
	"hyapp/pkg/appcode"
	"hyapp/pkg/xerr"
	"hyapp/services/wallet-service/internal/domain/ledger"
)

func (s *Server) GetRedPacketConfig(ctx context.Context, req *walletv1.GetRedPacketConfigRequest) (*walletv1.GetRedPacketConfigResponse, error) {
	ctx = appcode.WithContext(ctx, req.GetAppCode())
	config, err := s.svc.GetRedPacketConfig(ctx, req.GetAppCode())
	if err != nil {
		return nil, xerr.ToGRPCError(err)
	}
	return &walletv1.GetRedPacketConfigResponse{
		Config:       redPacketConfigToProto(config),
		ServerTimeMs: time.Now().UTC().UnixMilli(),
	}, nil
}

func (s *Server) UpdateRedPacketConfig(ctx context.Context, req *walletv1.UpdateRedPacketConfigRequest) (*walletv1.UpdateRedPacketConfigResponse, error) {
	ctx = appcode.WithContext(ctx, req.GetAppCode())
	config, err := s.svc.UpdateRedPacketConfig(ctx, ledger.RedPacketConfig{
		AppCode:            req.GetAppCode(),
		Enabled:            req.GetEnabled(),
		CountTiers:         req.GetCountTiers(),
		AmountTiers:        req.GetAmountTiers(),
		DelayedOpenSeconds: req.GetDelayedOpenSeconds(),
		ExpireSeconds:      req.GetExpireSeconds(),
		DailySendLimit:     req.GetDailySendLimit(),
		UpdatedByAdminID:   req.GetOperatorUserId(),
		RuleURL:            req.GetRuleUrl(),
	})
	if err != nil {
		return nil, xerr.ToGRPCError(err)
	}
	return &walletv1.UpdateRedPacketConfigResponse{
		Config:       redPacketConfigToProto(config),
		ServerTimeMs: time.Now().UTC().UnixMilli(),
	}, nil
}

func (s *Server) CreateRedPacket(ctx context.Context, req *walletv1.CreateRedPacketRequest) (*walletv1.CreateRedPacketResponse, error) {
	ctx = appcode.WithContext(ctx, req.GetAppCode())
	receipt, err := s.svc.CreateRedPacket(ctx, ledger.RedPacketCreateCommand{
		AppCode:      req.GetAppCode(),
		CommandID:    req.GetCommandId(),
		SenderUserID: req.GetSenderUserId(),
		RoomID:       req.GetRoomId(),
		RegionID:     req.GetRegionId(),
		PacketType:   req.GetPacketType(),
		TotalAmount:  req.GetTotalAmount(),
		PacketCount:  req.GetPacketCount(),
		RoomLocked:   req.GetRoomLocked(),
		OwnerUserID:  req.GetOwnerUserId(),
	})
	if err != nil {
		return nil, xerr.ToGRPCError(err)
	}
	return &walletv1.CreateRedPacketResponse{
		Packet:       redPacketToProto(receipt.Packet),
		BalanceAfter: receipt.BalanceAfter,
		ServerTimeMs: time.Now().UTC().UnixMilli(),
	}, nil
}

func (s *Server) ClaimRedPacket(ctx context.Context, req *walletv1.ClaimRedPacketRequest) (*walletv1.ClaimRedPacketResponse, error) {
	ctx = appcode.WithContext(ctx, req.GetAppCode())
	receipt, err := s.svc.ClaimRedPacket(ctx, ledger.RedPacketClaimCommand{
		AppCode:   req.GetAppCode(),
		CommandID: req.GetCommandId(),
		PacketID:  req.GetPacketId(),
		UserID:    req.GetUserId(),
	})
	if err != nil {
		return nil, xerr.ToGRPCError(err)
	}
	return &walletv1.ClaimRedPacketResponse{
		Claim:        redPacketClaimToProto(receipt.Claim),
		BalanceAfter: receipt.BalanceAfter,
		ServerTimeMs: time.Now().UTC().UnixMilli(),
		Packet:       redPacketToProto(receipt.Packet),
	}, nil
}

func (s *Server) ListRedPackets(ctx context.Context, req *walletv1.ListRedPacketsRequest) (*walletv1.ListRedPacketsResponse, error) {
	ctx = appcode.WithContext(ctx, req.GetAppCode())
	packets, total, err := s.svc.ListRedPackets(ctx, ledger.RedPacketQuery{
		AppCode:      req.GetAppCode(),
		RoomID:       req.GetRoomId(),
		SenderUserID: req.GetSenderUserId(),
		RegionID:     req.GetRegionId(),
		PacketType:   req.GetPacketType(),
		Status:       req.GetStatus(),
		ViewerUserID: req.GetViewerUserId(),
		StartAtMS:    req.GetStartAtMs(),
		EndAtMS:      req.GetEndAtMs(),
		Page:         req.GetPage(),
		PageSize:     req.GetPageSize(),
	})
	if err != nil {
		return nil, xerr.ToGRPCError(err)
	}
	resp := &walletv1.ListRedPacketsResponse{Packets: make([]*walletv1.RedPacket, 0, len(packets)), Total: total, ServerTimeMs: time.Now().UTC().UnixMilli()}
	for _, packet := range packets {
		resp.Packets = append(resp.Packets, redPacketToProto(packet))
	}
	return resp, nil
}

func (s *Server) GetRedPacket(ctx context.Context, req *walletv1.GetRedPacketRequest) (*walletv1.GetRedPacketResponse, error) {
	ctx = appcode.WithContext(ctx, req.GetAppCode())
	packet, err := s.svc.GetRedPacket(ctx, req.GetAppCode(), req.GetPacketId(), req.GetViewerUserId(), req.GetIncludeClaims())
	if err != nil {
		return nil, xerr.ToGRPCError(err)
	}
	return &walletv1.GetRedPacketResponse{Packet: redPacketToProto(packet), ServerTimeMs: time.Now().UTC().UnixMilli()}, nil
}

func (s *Server) ExpireRedPackets(ctx context.Context, req *walletv1.ExpireRedPacketsRequest) (*walletv1.ExpireRedPacketsResponse, error) {
	ctx = appcode.WithContext(ctx, req.GetAppCode())
	result, err := s.svc.ExpireRedPackets(ctx, req.GetAppCode(), req.GetBatchSize())
	if err != nil {
		return nil, xerr.ToGRPCError(err)
	}
	return &walletv1.ExpireRedPacketsResponse{
		ExpiredCount:   result.ExpiredCount,
		RefundedAmount: result.RefundedAmount,
		ServerTimeMs:   time.Now().UTC().UnixMilli(),
	}, nil
}

func (s *Server) RetryRedPacketRefund(ctx context.Context, req *walletv1.RetryRedPacketRefundRequest) (*walletv1.RetryRedPacketRefundResponse, error) {
	ctx = appcode.WithContext(ctx, req.GetAppCode())
	receipt, err := s.svc.RetryRedPacketRefund(ctx, req.GetAppCode(), req.GetPacketId(), req.GetOperatorUserId())
	if err != nil {
		return nil, xerr.ToGRPCError(err)
	}
	return &walletv1.RetryRedPacketRefundResponse{
		Packet:         redPacketToProto(receipt.Packet),
		RefundedAmount: receipt.RefundedAmount,
		ServerTimeMs:   time.Now().UTC().UnixMilli(),
	}, nil
}

func redPacketConfigToProto(config ledger.RedPacketConfig) *walletv1.RedPacketConfig {
	return &walletv1.RedPacketConfig{
		AppCode:            config.AppCode,
		Enabled:            config.Enabled,
		CountTiers:         config.CountTiers,
		AmountTiers:        config.AmountTiers,
		DelayedOpenSeconds: config.DelayedOpenSeconds,
		ExpireSeconds:      config.ExpireSeconds,
		DailySendLimit:     config.DailySendLimit,
		UpdatedByAdminId:   config.UpdatedByAdminID,
		CreatedAtMs:        config.CreatedAtMS,
		UpdatedAtMs:        config.UpdatedAtMS,
		RuleUrl:            config.RuleURL,
	}
}

func redPacketToProto(packet ledger.RedPacket) *walletv1.RedPacket {
	claims := make([]*walletv1.RedPacketClaim, 0, len(packet.Claims))
	for _, claim := range packet.Claims {
		claims = append(claims, redPacketClaimToProto(claim))
	}
	return &walletv1.RedPacket{
		AppCode:           packet.AppCode,
		PacketId:          packet.PacketID,
		CommandId:         packet.CommandID,
		SenderUserId:      packet.SenderUserID,
		RoomId:            packet.RoomID,
		RegionId:          packet.RegionID,
		PacketType:        packet.PacketType,
		TotalAmount:       packet.TotalAmount,
		PacketCount:       packet.PacketCount,
		RemainingAmount:   packet.RemainingAmount,
		RemainingCount:    packet.RemainingCount,
		Status:            packet.Status,
		OpenAtMs:          packet.OpenAtMS,
		ExpiresAtMs:       packet.ExpiresAtMS,
		CreatedAtMs:       packet.CreatedAtMS,
		UpdatedAtMs:       packet.UpdatedAtMS,
		ClaimedByViewer:   packet.ClaimedByViewer,
		ViewerClaimAmount: packet.ViewerClaimAmount,
		Claims:            claims,
		RefundAmount:      packet.RefundAmount,
		RefundStatus:      packet.RefundStatus,
		RefundedAtMs:      packet.RefundedAtMS,
	}
}

func redPacketClaimToProto(claim ledger.RedPacketClaim) *walletv1.RedPacketClaim {
	return &walletv1.RedPacketClaim{
		AppCode:             claim.AppCode,
		ClaimId:             claim.ClaimID,
		CommandId:           claim.CommandID,
		PacketId:            claim.PacketID,
		UserId:              claim.UserID,
		Amount:              claim.Amount,
		WalletTransactionId: claim.WalletTransactionID,
		Status:              claim.Status,
		FailureReason:       claim.FailureReason,
		CreatedAtMs:         claim.CreatedAtMS,
		UpdatedAtMs:         claim.UpdatedAtMS,
	}
}
