package service

import (
	"context"
	"fmt"
	"time"

	"github.com/genex/trading-service/internal/domain/entity"
	"github.com/genex/trading-service/internal/domain/event"
	"github.com/genex/trading-service/internal/domain/repository"
	"github.com/genex/trading-service/internal/domain/vo"
)

// TradeService is the application-level use-case service for trading operations.
// It coordinates between the matching engine, repositories, and event publishing.
type TradeService struct {
	orderRepo      repository.OrderRepository
	tradeRepo      repository.TradeRepository
	matchingService *MatchingService
	publisher      event.EventPublisher
}

// NewTradeService creates a new TradeService with all dependencies injected.
func NewTradeService(
	orderRepo repository.OrderRepository,
	tradeRepo repository.TradeRepository,
	matchingService *MatchingService,
	publisher event.EventPublisher,
) *TradeService {
	if publisher == nil {
		publisher = &event.NoopEventPublisher{}
	}
	return &TradeService{
		orderRepo:       orderRepo,
		tradeRepo:       tradeRepo,
		matchingService: matchingService,
		publisher:       publisher,
	}
}

// PlaceOrderInput carries the validated input for placing an order.
type PlaceOrderInput struct {
	UserID   string
	CouponID string
	Side     vo.OrderSide
	Type     vo.OrderType
	Price    vo.Price
	Quantity vo.Quantity
}

// PlaceOrderOutput is the result of a place order use case.
type PlaceOrderOutput struct {
	Order  *entity.Order
	Trades []*entity.Trade
}

// PlaceOrder handles the full workflow of placing an order:
// 1. Create the domain entity
// 2. Persist the order
// 3. Submit to matching engine
// 4. Persist resulting trades
// 5. Update order status
func (s *TradeService) PlaceOrder(ctx context.Context, input PlaceOrderInput) (*PlaceOrderOutput, error) {
	// Generate order ID
	orderID := fmt.Sprintf("ord-%d", time.Now().UnixNano())

	// Create domain entity with validation
	order, err := entity.NewOrder(
		orderID, input.UserID, input.CouponID,
		input.Side, input.Type, input.Price, input.Quantity,
	)
	if err != nil {
		return nil, fmt.Errorf("invalid order: %w", err)
	}

	// Persist the new order
	if err := s.orderRepo.Save(ctx, order); err != nil {
		return nil, fmt.Errorf("failed to save order: %w", err)
	}

	// Submit to matching engine
	matchResult := s.matchingService.PlaceOrder(order)

	// Persist resulting trades
	for _, trade := range matchResult.Trades {
		if err := s.tradeRepo.Save(ctx, trade); err != nil {
			return nil, fmt.Errorf("failed to save trade %s: %w", trade.ID, err)
		}
	}

	// Update order status after matching
	if err := s.orderRepo.UpdateStatus(ctx, order); err != nil {
		return nil, fmt.Errorf("failed to update order status: %w", err)
	}

	return &PlaceOrderOutput{
		Order:  matchResult.UpdatedOrder,
		Trades: matchResult.Trades,
	}, nil
}

// CancelOrder cancels an active order by removing it from the order book.
func (s *TradeService) CancelOrder(ctx context.Context, couponID, orderID string, side vo.OrderSide) error {
	success := s.matchingService.CancelOrder(couponID, orderID, side)
	if !success {
		return fmt.Errorf("order not found in order book: %s", orderID)
	}

	// Update order status in repository
	order, err := s.orderRepo.FindByID(ctx, orderID)
	if err == nil && order != nil {
		order.Cancel()
		_ = s.orderRepo.UpdateStatus(ctx, order)
	}

	return nil
}

// GetOrdersByUser retrieves all orders for a given user.
func (s *TradeService) GetOrdersByUser(ctx context.Context, userID string) ([]*entity.Order, error) {
	return s.orderRepo.FindByUserID(ctx, userID)
}

// GetOrder retrieves a single order by ID.
func (s *TradeService) GetOrder(ctx context.Context, orderID string) (*entity.Order, error) {
	return s.orderRepo.FindByID(ctx, orderID)
}

// GetTradesByOrder retrieves all trades associated with an order.
func (s *TradeService) GetTradesByOrder(ctx context.Context, orderID string) ([]*entity.Trade, error) {
	return s.tradeRepo.FindByOrderID(ctx, orderID)
}

// GetRecentTrades retrieves the most recent trades.
func (s *TradeService) GetRecentTrades(ctx context.Context, limit int) ([]*entity.Trade, error) {
	return s.tradeRepo.FindRecent(ctx, limit)
}

// GetOrderBookSnapshot delegates to the matching service for order book data.
func (s *TradeService) GetOrderBookSnapshot(couponID string, depth int) (bids []entity.PriceLevel, asks []entity.PriceLevel) {
	return s.matchingService.GetOrderBookSnapshot(couponID, depth)
}

// GetOrdersByUserPaginated retrieves paginated orders for a user with optional status filter.
func (s *TradeService) GetOrdersByUserPaginated(ctx context.Context, userID string, status string, page, limit int) ([]*entity.Order, int, error) {
	offset := (page - 1) * limit
	return s.orderRepo.FindByUserIDPaginated(ctx, userID, status, offset, limit)
}

// TransferCoupon validates ownership and transfers a coupon to a new owner by updating the order record.
func (s *TradeService) TransferCoupon(ctx context.Context, couponID, ownerUserID, recipientID string) error {
	// Validate that the user actually owns this coupon by checking filled buy orders
	orders, err := s.orderRepo.FindByCouponID(ctx, couponID)
	if err != nil {
		return fmt.Errorf("failed to look up coupon orders: %w", err)
	}

	// Verify the user has a filled buy order for this coupon (ownership proof)
	ownsIt := false
	for _, o := range orders {
		if o.UserID == ownerUserID && o.Status == entity.OrderFilled && o.Side == vo.Buy {
			ownsIt = true
			break
		}
	}
	if !ownsIt {
		return fmt.Errorf("user does not own coupon %s", couponID)
	}

	// Create a transfer record as a special filled order pair
	transferID := fmt.Sprintf("xfr-%d", time.Now().UnixNano())
	transferOrder, err := entity.NewOrder(
		transferID, recipientID, couponID,
		vo.Buy, vo.Market, vo.ZeroPrice(), vo.MustNewQuantity(1),
	)
	if err != nil {
		return fmt.Errorf("failed to create transfer order: %w", err)
	}
	transferOrder.Status = entity.OrderFilled
	transferOrder.FilledQty = vo.MustNewQuantity(1)
	transferOrder.RemainingQty = vo.ZeroQuantity()

	return s.orderRepo.Save(ctx, transferOrder)
}

// GetAllOrderBooks returns all active order books (admin use).
func (s *TradeService) GetAllOrderBooks() map[string]*entity.OrderBook {
	return s.matchingService.GetAllOrderBooks()
}

// GetTradeCount returns the total trade count.
func (s *TradeService) GetTradeCount() int64 {
	return s.matchingService.GetTradeCount()
}
