package service

import (
	"context"
	"testing"
	"time"

	"apskel-pos-be/internal/entities"
	"apskel-pos-be/internal/models"

	"github.com/google/uuid"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/mock"
)

// Mock implementations for testing
type MockOrderProcessor struct {
	mock.Mock
}

func (m *MockOrderProcessor) CreateOrder(ctx context.Context, req *models.CreateOrderRequest, organizationID uuid.UUID) (*models.OrderResponse, error) {
	args := m.Called(ctx, req, organizationID)
	if args.Get(0) == nil {
		return nil, args.Error(1)
	}
	return args.Get(0).(*models.OrderResponse), args.Error(1)
}

func (m *MockOrderProcessor) AddToOrder(ctx context.Context, orderID uuid.UUID, req *models.AddToOrderRequest) (*models.AddToOrderResponse, error) {
	args := m.Called(ctx, orderID, req)
	if args.Get(0) == nil {
		return nil, args.Error(1)
	}
	return args.Get(0).(*models.AddToOrderResponse), args.Error(1)
}

func (m *MockOrderProcessor) UpdateOrder(ctx context.Context, id uuid.UUID, req *models.UpdateOrderRequest) (*models.OrderResponse, error) {
	args := m.Called(ctx, id, req)
	if args.Get(0) == nil {
		return nil, args.Error(1)
	}
	return args.Get(0).(*models.OrderResponse), args.Error(1)
}

func (m *MockOrderProcessor) GetOrderByID(ctx context.Context, id uuid.UUID) (*models.OrderResponse, error) {
	args := m.Called(ctx, id)
	if args.Get(0) == nil {
		return nil, args.Error(1)
	}
	return args.Get(0).(*models.OrderResponse), args.Error(1)
}

func (m *MockOrderProcessor) ListOrders(ctx context.Context, req *models.ListOrdersRequest) (*models.ListOrdersResponse, error) {
	args := m.Called(ctx, req)
	if args.Get(0) == nil {
		return nil, args.Error(1)
	}
	return args.Get(0).(*models.ListOrdersResponse), args.Error(1)
}

func (m *MockOrderProcessor) VoidOrder(ctx context.Context, req *models.VoidOrderRequest, voidedBy uuid.UUID) error {
	args := m.Called(ctx, req, voidedBy)
	return args.Error(0)
}

func (m *MockOrderProcessor) RefundOrder(ctx context.Context, id uuid.UUID, req *models.RefundOrderRequest, refundedBy uuid.UUID) error {
	args := m.Called(ctx, id, req, refundedBy)
	return args.Error(0)
}

func (m *MockOrderProcessor) CreatePayment(ctx context.Context, req *models.CreatePaymentRequest) (*models.PaymentResponse, error) {
	args := m.Called(ctx, req)
	if args.Get(0) == nil {
		return nil, args.Error(1)
	}
	return args.Get(0).(*models.PaymentResponse), args.Error(1)
}

func (m *MockOrderProcessor) RefundPayment(ctx context.Context, paymentID uuid.UUID, refundAmount float64, reason string, refundedBy uuid.UUID) error {
	args := m.Called(ctx, paymentID, refundAmount, reason, refundedBy)
	return args.Error(0)
}

func (m *MockOrderProcessor) SetOrderCustomer(ctx context.Context, orderID uuid.UUID, req *models.SetOrderCustomerRequest, organizationID uuid.UUID) (*models.SetOrderCustomerResponse, error) {
	args := m.Called(ctx, orderID, req, organizationID)
	if args.Get(0) == nil {
		return nil, args.Error(1)
	}
	return args.Get(0).(*models.SetOrderCustomerResponse), args.Error(1)
}

func (m *MockOrderProcessor) SplitBill(ctx context.Context, req *models.SplitBillRequest) (*models.SplitBillResponse, error) {
	args := m.Called(ctx, req)
	if args.Get(0) == nil {
		return nil, args.Error(1)
	}
	return args.Get(0).(*models.SplitBillResponse), args.Error(1)
}

type MockTableRepository struct {
	mock.Mock
}

func (m *MockTableRepository) Create(ctx context.Context, table *entities.Table) error {
	args := m.Called(ctx, table)
	return args.Error(0)
}

func (m *MockTableRepository) GetByID(ctx context.Context, id uuid.UUID) (*entities.Table, error) {
	args := m.Called(ctx, id)
	if args.Get(0) == nil {
		return nil, args.Error(1)
	}
	return args.Get(0).(*entities.Table), args.Error(1)
}

func (m *MockTableRepository) GetByOutletID(ctx context.Context, outletID uuid.UUID) ([]entities.Table, error) {
	args := m.Called(ctx, outletID)
	if args.Get(0) == nil {
		return nil, args.Error(1)
	}
	return args.Get(0).([]entities.Table), args.Error(1)
}

func (m *MockTableRepository) GetByOrganizationID(ctx context.Context, organizationID uuid.UUID) ([]entities.Table, error) {
	args := m.Called(ctx, organizationID)
	if args.Get(0) == nil {
		return nil, args.Error(1)
	}
	return args.Get(0).([]entities.Table), args.Error(1)
}

func (m *MockTableRepository) Update(ctx context.Context, table *entities.Table) error {
	args := m.Called(ctx, table)
	return args.Error(0)
}

func (m *MockTableRepository) Delete(ctx context.Context, id uuid.UUID) error {
	args := m.Called(ctx, id)
	return args.Error(0)
}

func (m *MockTableRepository) List(ctx context.Context, organizationID, outletID *uuid.UUID, status *string, isActive *bool, search string, page, limit int) ([]entities.Table, int64, error) {
	args := m.Called(ctx, organizationID, outletID, status, isActive, search, page, limit)
	if args.Get(0) == nil {
		return nil, 0, args.Error(2)
	}
	return args.Get(0).([]entities.Table), args.Get(1).(int64), args.Error(2)
}

func (m *MockTableRepository) GetAvailableTables(ctx context.Context, outletID uuid.UUID) ([]entities.Table, error) {
	args := m.Called(ctx, outletID)
	if args.Get(0) == nil {
		return nil, args.Error(1)
	}
	return args.Get(0).([]entities.Table), args.Error(1)
}

func (m *MockTableRepository) GetOccupiedTables(ctx context.Context, outletID uuid.UUID) ([]entities.Table, error) {
	args := m.Called(ctx, outletID)
	if args.Get(0) == nil {
		return nil, args.Error(1)
	}
	return args.Get(0).([]entities.Table), args.Error(1)
}

func (m *MockTableRepository) OccupyTable(ctx context.Context, tableID, orderID uuid.UUID, startTime *time.Time) error {
	args := m.Called(ctx, tableID, orderID, startTime)
	return args.Error(0)
}

func (m *MockTableRepository) ReleaseTable(ctx context.Context, tableID uuid.UUID, paymentAmount float64) error {
	args := m.Called(ctx, tableID, paymentAmount)
	return args.Error(0)
}

func (m *MockTableRepository) GetByOrderID(ctx context.Context, orderID uuid.UUID) (*entities.Table, error) {
	args := m.Called(ctx, orderID)
	if args.Get(0) == nil {
		return nil, args.Error(1)
	}
	return args.Get(0).(*entities.Table), args.Error(1)
}

func TestCreateOrderWithTableOccupation(t *testing.T) {
	// Setup
	ctx := context.Background()
	organizationID := uuid.New()
	outletID := uuid.New()
	userID := uuid.New()
	tableID := uuid.New()
	orderID := uuid.New()

	// Create mock table
	mockTable := &entities.Table{
		ID:             tableID,
		OrganizationID: organizationID,
		OutletID:       outletID,
		TableName:      "Table 1",
		Status:         "available",
		IsActive:       true,
	}

	// Create mock order response
	mockOrderResponse := &models.OrderResponse{
		ID: orderID,
		// Add other required fields
	}

	// Create mock repositories
	mockTableRepo := &MockTableRepository{}
	mockOrderProcessor := &MockOrderProcessor{}

	// Set up expectations
	mockTableRepo.On("GetByID", ctx, tableID).Return(mockTable, nil)
	mockOrderProcessor.On("CreateOrder", ctx, mock.AnythingOfType("*models.CreateOrderRequest"), organizationID).Return(mockOrderResponse, nil)
	mockTableRepo.On("OccupyTable", ctx, tableID, orderID, mock.AnythingOfType("*time.Time")).Return(nil)

	// Create service with mock dependencies
	service := &OrderServiceImpl{
		orderProcessor: mockOrderProcessor,
		tableRepo:      mockTableRepo,
	}

	// Create test request
	req := &models.CreateOrderRequest{
		OutletID:  outletID,
		UserID:    userID,
		TableID:   &tableID,
		OrderType: "dine_in",
		OrderItems: []models.CreateOrderItemRequest{
			{
				ProductID: uuid.New(),
				Quantity:  1,
			},
		},
	}

	// Test order creation with table occupation
	response, err := service.CreateOrder(ctx, req, organizationID)

	// Assertions
	assert.NoError(t, err)
	assert.NotNil(t, response)
	assert.Equal(t, orderID, response.ID)

	// Verify mock calls
	mockTableRepo.AssertExpectations(t)
	mockOrderProcessor.AssertExpectations(t)
}

func TestCreateOrderTableValidationFailure(t *testing.T) {
	// Setup
	ctx := context.Background()
	organizationID := uuid.New()
	outletID := uuid.New()
	userID := uuid.New()
	tableID := uuid.New()

	// Create mock table that's already occupied
	mockTable := &entities.Table{
		ID:             tableID,
		OrganizationID: organizationID,
		OutletID:       outletID,
		TableName:      "Table 1",
		Status:         "occupied", // Already occupied
		IsActive:       true,
	}

	// Create mock repositories
	mockTableRepo := &MockTableRepository{}
	mockOrderProcessor := &MockOrderProcessor{}

	// Set up expectations
	mockTableRepo.On("GetByID", ctx, tableID).Return(mockTable, nil)

	// Create service with mock dependencies
	service := &OrderServiceImpl{
		orderProcessor: mockOrderProcessor,
		tableRepo:      mockTableRepo,
	}

	// Create test request
	req := &models.CreateOrderRequest{
		OutletID:  outletID,
		UserID:    userID,
		TableID:   &tableID,
		OrderType: "dine_in",
		OrderItems: []models.CreateOrderItemRequest{
			{
				ProductID: uuid.New(),
				Quantity:  1,
			},
		},
	}

	// Test order creation with occupied table
	response, err := service.CreateOrder(ctx, req, organizationID)

	// Assertions
	assert.Error(t, err)
	assert.Nil(t, response)
	assert.Contains(t, err.Error(), "table is not available for occupation")

	// Verify mock calls
	mockTableRepo.AssertExpectations(t)
	mockOrderProcessor.AssertNotCalled(t, "CreateOrder")
}
