package postgres

import (
	"context"
	"fmt"
	"time"

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

// Compile-time check: PostgresOrderRepository implements repository.OrderRepository.
var _ repository.OrderRepository = (*PostgresOrderRepository)(nil)

// orderModel is the GORM persistence model for the orders table.
// It lives in the infrastructure layer, keeping the domain entity free of ORM concerns.
type orderModel struct {
	ID             string     `gorm:"column:id;primaryKey"`
	UserID         string     `gorm:"column:user_id;not null"`
	CouponID       string     `gorm:"column:coupon_id;not null"`
	Side           string     `gorm:"column:side;not null"`
	OrderType      string     `gorm:"column:order_type;not null;default:limit"`
	Price          float64    `gorm:"column:price;not null"`
	Quantity       int        `gorm:"column:quantity;not null;default:1"`
	FilledQuantity int        `gorm:"column:filled_quantity;not null;default:0"`
	Status         string     `gorm:"column:status;not null;default:open"`
	IsMaker        bool       `gorm:"column:is_maker;not null;default:false"`
	CancelledAt    *time.Time `gorm:"column:cancelled_at"`
	CreatedAt      time.Time  `gorm:"column:created_at;autoCreateTime"`
	UpdatedAt      time.Time  `gorm:"column:updated_at;autoUpdateTime"`
}

func (orderModel) TableName() string { return "orders" }

// dbStatusToDomain maps database status values to domain OrderStatus.
func dbStatusToDomain(dbStatus string) entity.OrderStatus {
	switch dbStatus {
	case "open":
		return entity.OrderPending
	case "partial":
		return entity.OrderPartial
	case "filled":
		return entity.OrderFilled
	case "cancelled":
		return entity.OrderCancelled
	default:
		return entity.OrderStatus(dbStatus)
	}
}

// domainStatusToDB maps domain OrderStatus to database status values.
func domainStatusToDB(status entity.OrderStatus) string {
	switch status {
	case entity.OrderPending:
		return "open"
	case entity.OrderPartial:
		return "partial"
	case entity.OrderFilled:
		return "filled"
	case entity.OrderCancelled:
		return "cancelled"
	default:
		return string(status)
	}
}

func (m *orderModel) toEntity() *entity.Order {
	remaining := m.Quantity - m.FilledQuantity
	if remaining < 0 {
		remaining = 0
	}
	return &entity.Order{
		ID:           m.ID,
		UserID:       m.UserID,
		CouponID:     m.CouponID,
		Side:         vo.OrderSide(m.Side),
		Type:         vo.OrderType(m.OrderType),
		Price:        vo.MustNewPrice(m.Price),
		Quantity:     vo.QuantityFromInt(m.Quantity),
		FilledQty:    vo.QuantityFromInt(m.FilledQuantity),
		RemainingQty: vo.QuantityFromInt(remaining),
		Status:       dbStatusToDomain(m.Status),
		CreatedAt:    m.CreatedAt,
		UpdatedAt:    m.UpdatedAt,
	}
}

func orderFromEntity(e *entity.Order) *orderModel {
	var cancelledAt *time.Time
	if e.Status == entity.OrderCancelled {
		now := time.Now()
		cancelledAt = &now
	}
	return &orderModel{
		ID:             e.ID,
		UserID:         e.UserID,
		CouponID:       e.CouponID,
		Side:           string(e.Side),
		OrderType:      string(e.Type),
		Price:          e.Price.Float64(),
		Quantity:       e.Quantity.Int(),
		FilledQuantity: e.FilledQty.Int(),
		Status:         domainStatusToDB(e.Status),
		CancelledAt:    cancelledAt,
		CreatedAt:      e.CreatedAt,
		UpdatedAt:      e.UpdatedAt,
	}
}

// PostgresOrderRepository is the GORM-backed implementation of repository.OrderRepository.
type PostgresOrderRepository struct {
	db *gorm.DB
}

// NewPostgresOrderRepository creates a new repository backed by PostgreSQL via GORM.
func NewPostgresOrderRepository(db *gorm.DB) *PostgresOrderRepository {
	return &PostgresOrderRepository{db: db}
}

func (r *PostgresOrderRepository) Save(ctx context.Context, order *entity.Order) error {
	if order == nil {
		return fmt.Errorf("order must not be nil")
	}
	model := orderFromEntity(order)
	return r.db.WithContext(ctx).Save(model).Error
}

func (r *PostgresOrderRepository) FindByID(ctx context.Context, id string) (*entity.Order, error) {
	var model orderModel
	err := r.db.WithContext(ctx).Where("id = ?", id).First(&model).Error
	if err != nil {
		if err == gorm.ErrRecordNotFound {
			return nil, fmt.Errorf("order not found: %s", id)
		}
		return nil, err
	}
	return model.toEntity(), nil
}

func (r *PostgresOrderRepository) FindByUserID(ctx context.Context, userID string) ([]*entity.Order, error) {
	var models []orderModel
	err := r.db.WithContext(ctx).Where("user_id = ?", userID).Order("created_at DESC").Find(&models).Error
	if err != nil {
		return nil, err
	}
	result := make([]*entity.Order, len(models))
	for i := range models {
		result[i] = models[i].toEntity()
	}
	return result, nil
}

func (r *PostgresOrderRepository) FindActive(ctx context.Context) ([]*entity.Order, error) {
	var models []orderModel
	err := r.db.WithContext(ctx).Where("status IN ?", []string{"open", "partial"}).Order("created_at ASC").Find(&models).Error
	if err != nil {
		return nil, err
	}
	result := make([]*entity.Order, len(models))
	for i := range models {
		result[i] = models[i].toEntity()
	}
	return result, nil
}

func (r *PostgresOrderRepository) FindByCouponID(ctx context.Context, couponID string) ([]*entity.Order, error) {
	var models []orderModel
	err := r.db.WithContext(ctx).Where("coupon_id = ?", couponID).Order("created_at DESC").Find(&models).Error
	if err != nil {
		return nil, err
	}
	result := make([]*entity.Order, len(models))
	for i := range models {
		result[i] = models[i].toEntity()
	}
	return result, nil
}

func (r *PostgresOrderRepository) UpdateStatus(ctx context.Context, order *entity.Order) error {
	if order == nil {
		return fmt.Errorf("order must not be nil")
	}
	model := orderFromEntity(order)
	return r.db.WithContext(ctx).Save(model).Error
}

func (r *PostgresOrderRepository) FindAll(ctx context.Context, offset, limit int) ([]*entity.Order, int, error) {
	var models []orderModel
	var total int64

	base := r.db.WithContext(ctx).Model(&orderModel{})
	if err := base.Count(&total).Error; err != nil {
		return nil, 0, err
	}

	if err := base.Order("created_at DESC").Offset(offset).Limit(limit).Find(&models).Error; err != nil {
		return nil, 0, err
	}

	result := make([]*entity.Order, len(models))
	for i := range models {
		result[i] = models[i].toEntity()
	}
	return result, int(total), nil
}

func (r *PostgresOrderRepository) FindByUserIDPaginated(ctx context.Context, userID string, status string, offset, limit int) ([]*entity.Order, int, error) {
	var models []orderModel
	var total int64

	base := r.db.WithContext(ctx).Model(&orderModel{}).Where("user_id = ?", userID)
	if status != "" {
		base = base.Where("status = ?", status)
	}

	if err := base.Count(&total).Error; err != nil {
		return nil, 0, err
	}

	if err := base.Order("created_at DESC").Offset(offset).Limit(limit).Find(&models).Error; err != nil {
		return nil, 0, err
	}

	result := make([]*entity.Order, len(models))
	for i := range models {
		result[i] = models[i].toEntity()
	}
	return result, int(total), nil
}
