package repository

import (
	"context"
	"fmt"
	"time"

	"apskel-pos-be/internal/entities"

	"github.com/google/uuid"
	"gorm.io/gorm"
)

type OrderRepository interface {
	Create(ctx context.Context, order *entities.Order) error
	GetByID(ctx context.Context, id uuid.UUID) (*entities.Order, error)
	GetWithRelations(ctx context.Context, id uuid.UUID) (*entities.Order, error)
	Update(ctx context.Context, order *entities.Order) error
	Delete(ctx context.Context, id uuid.UUID) error
	List(ctx context.Context, filters map[string]interface{}, limit, offset int) ([]*entities.Order, int64, error)
	GetByOrderNumber(ctx context.Context, orderNumber string) (*entities.Order, error)
	ExistsByOrderNumber(ctx context.Context, orderNumber string) (bool, error)
	VoidOrder(ctx context.Context, id uuid.UUID, reason string, voidedBy uuid.UUID) error
	VoidOrderWithStatus(ctx context.Context, id uuid.UUID, status entities.OrderStatus, reason string, voidedBy uuid.UUID) error
	RefundOrder(ctx context.Context, id uuid.UUID, reason string, refundedBy uuid.UUID) error
	UpdatePaymentStatus(ctx context.Context, id uuid.UUID, status entities.PaymentStatus) error
	UpdateStatus(ctx context.Context, id uuid.UUID, status entities.OrderStatus) error
	GetNextOrderNumber(ctx context.Context, organizationID, outletID uuid.UUID) (string, error)
}

type OrderRepositoryImpl struct {
	db *gorm.DB
}

func NewOrderRepositoryImpl(db *gorm.DB) *OrderRepositoryImpl {
	return &OrderRepositoryImpl{
		db: db,
	}
}

func (r *OrderRepositoryImpl) Create(ctx context.Context, order *entities.Order) error {
	return r.db.WithContext(ctx).Create(order).Error
}

func (r *OrderRepositoryImpl) GetByID(ctx context.Context, id uuid.UUID) (*entities.Order, error) {
	var order entities.Order
	err := r.db.WithContext(ctx).First(&order, "id = ?", id).Error
	if err != nil {
		return nil, err
	}
	return &order, nil
}

func (r *OrderRepositoryImpl) GetWithRelations(ctx context.Context, id uuid.UUID) (*entities.Order, error) {
	var order entities.Order
	err := r.db.WithContext(ctx).
		Preload("Organization").
		Preload("Outlet").
		Preload("User").
		Preload("OrderItems").
		Preload("OrderItems.Product").
		Preload("OrderItems.ProductVariant").
		Preload("Payments").
		Preload("Payments.PaymentMethod").
		Preload("Payments.PaymentOrderItems").
		First(&order, "id = ?", id).Error
	if err != nil {
		return nil, err
	}
	return &order, nil
}

func (r *OrderRepositoryImpl) Update(ctx context.Context, order *entities.Order) error {
	return r.db.WithContext(ctx).Save(order).Error
}

func (r *OrderRepositoryImpl) UpdateStatusSuccess(
	ctx context.Context,
	id uuid.UUID,
	orderStatus entities.OrderStatus,
	paymentStatus entities.PaymentStatus,
) error {
	return r.db.WithContext(ctx).
		Model(&entities.Order{}).
		Where("id = ?", id.String()).
		Updates(map[string]interface{}{
			"status":         orderStatus,
			"payment_status": paymentStatus,
		}).Error
}

func (r *OrderRepositoryImpl) Delete(ctx context.Context, id uuid.UUID) error {
	return r.db.WithContext(ctx).Delete(&entities.Order{}, "id = ?", id).Error
}

func (r *OrderRepositoryImpl) List(ctx context.Context, filters map[string]interface{}, limit, offset int) ([]*entities.Order, int64, error) {
	var orders []*entities.Order
	var total int64

	query := r.db.WithContext(ctx).Model(&entities.Order{}).
		Preload("Organization").
		Preload("Outlet").
		Preload("User").
		Preload("OrderItems").
		Preload("OrderItems.Product").
		Preload("OrderItems.ProductVariant").
		Preload("Payments").
		Preload("Payments.PaymentMethod").
		Preload("Payments.PaymentOrderItems")

	for key, value := range filters {
		switch key {
		case "search":
			searchValue := "%" + value.(string) + "%"
			query = query.Where("order_number ILIKE ?", searchValue)
		case "date_from":
			query = query.Where("created_at >= ?", value)
		case "date_to":
			query = query.Where("created_at <= ?", value)
		default:
			query = query.Where(key+" = ?", value)
		}
	}

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

	err := query.Limit(limit).Offset(offset).Order("created_at DESC").Find(&orders).Error
	return orders, total, err
}

func (r *OrderRepositoryImpl) GetByOrderNumber(ctx context.Context, orderNumber string) (*entities.Order, error) {
	var order entities.Order
	err := r.db.WithContext(ctx).First(&order, "order_number = ?", orderNumber).Error
	if err != nil {
		return nil, err
	}
	return &order, nil
}

func (r *OrderRepositoryImpl) ExistsByOrderNumber(ctx context.Context, orderNumber string) (bool, error) {
	var count int64
	err := r.db.WithContext(ctx).Model(&entities.Order{}).Where("order_number = ?", orderNumber).Count(&count).Error
	return count > 0, err
}

func (r *OrderRepositoryImpl) VoidOrder(ctx context.Context, id uuid.UUID, reason string, voidedBy uuid.UUID) error {
	now := time.Now()
	return r.db.WithContext(ctx).Model(&entities.Order{}).
		Where("id = ?", id).
		Updates(map[string]interface{}{
			"is_void":     true,
			"void_reason": reason,
			"voided_at":   now,
			"voided_by":   voidedBy,
		}).Error
}

func (r *OrderRepositoryImpl) VoidOrderWithStatus(ctx context.Context, id uuid.UUID, status entities.OrderStatus, reason string, voidedBy uuid.UUID) error {
	now := time.Now()
	return r.db.WithContext(ctx).Model(&entities.Order{}).
		Where("id = ?", id).
		Updates(map[string]interface{}{
			"status":      status,
			"is_void":     true,
			"void_reason": reason,
			"voided_at":   now,
			"voided_by":   voidedBy,
		}).Error
}

func (r *OrderRepositoryImpl) RefundOrder(ctx context.Context, id uuid.UUID, reason string, refundedBy uuid.UUID) error {
	now := time.Now()
	return r.db.WithContext(ctx).Model(&entities.Order{}).
		Where("id = ?", id).
		Updates(map[string]interface{}{
			"is_refund":     true,
			"refund_reason": reason,
			"refunded_at":   now,
			"refunded_by":   refundedBy,
		}).Error
}

func (r *OrderRepositoryImpl) UpdatePaymentStatus(ctx context.Context, id uuid.UUID, status entities.PaymentStatus) error {
	return r.db.WithContext(ctx).Model(&entities.Order{}).
		Where("id = ?", id).
		Update("payment_status", status).Error
}

func (r *OrderRepositoryImpl) UpdateStatus(ctx context.Context, id uuid.UUID, status entities.OrderStatus) error {
	return r.db.WithContext(ctx).Model(&entities.Order{}).
		Where("id = ?", id).
		Update("status", status).Error
}

func (r *OrderRepositoryImpl) GetNextOrderNumber(ctx context.Context, organizationID, outletID uuid.UUID) (string, error) {
	now := time.Now()
	year := now.Year()
	month := int(now.Month())

	// Use a transaction to ensure atomic sequence increment
	tx := r.db.WithContext(ctx).Begin()
	if tx.Error != nil {
		return "", tx.Error
	}
	defer func() {
		if r := recover(); r != nil {
			tx.Rollback()
		}
	}()

	// Get or create sequence record
	var sequence entities.OrderSequence
	err := tx.Where("organization_id = ? AND outlet_id = ? AND year = ? AND month = ?",
		organizationID, outletID, year, month).
		First(&sequence).Error

	if err != nil {
		if err == gorm.ErrRecordNotFound {
			// Create new sequence record
			sequence = entities.OrderSequence{
				OrganizationID: organizationID,
				OutletID:       outletID,
				Year:           year,
				Month:          month,
				SequenceNumber: 0,
			}
			if err := tx.Create(&sequence).Error; err != nil {
				tx.Rollback()
				return "", err
			}
		} else {
			tx.Rollback()
			return "", err
		}
	}

	// Increment sequence number
	sequence.SequenceNumber++
	if err := tx.Save(&sequence).Error; err != nil {
		tx.Rollback()
		return "", err
	}

	// Commit transaction
	if err := tx.Commit().Error; err != nil {
		return "", err
	}

	orderNumber := fmt.Sprintf("ORD/%04d%02d/%06d", year, month, sequence.SequenceNumber)
	return orderNumber, nil
}
