package processor

import (
	"apskel-pos-be/internal/constants"
	"apskel-pos-be/internal/entities"
	"apskel-pos-be/internal/models"
	"apskel-pos-be/internal/repository"
	"context"
	"errors"
	"math"

	"github.com/google/uuid"
)

type TableProcessor struct {
	tableRepo repository.TableRepositoryInterface
	orderRepo repository.OrderRepository
}

func NewTableProcessor(tableRepo repository.TableRepositoryInterface, orderRepo repository.OrderRepository) *TableProcessor {
	return &TableProcessor{
		tableRepo: tableRepo,
		orderRepo: orderRepo,
	}
}

func (p *TableProcessor) Create(ctx context.Context, req models.CreateTableRequest, organizationID uuid.UUID) (*models.TableResponse, error) {
	table := &entities.Table{
		OrganizationID: organizationID,
		OutletID:       req.OutletID,
		TableName:      req.TableName,
		PositionX:      req.PositionX,
		PositionY:      req.PositionY,
		Capacity:       req.Capacity,
		Status:         string(constants.TableStatusAvailable),
		IsActive:       true,
		Metadata:       req.Metadata,
	}

	err := p.tableRepo.Create(ctx, table)
	if err != nil {
		return nil, err
	}

	return p.mapTableToResponse(table), nil
}

func (p *TableProcessor) GetByID(ctx context.Context, id uuid.UUID) (*models.TableResponse, error) {
	table, err := p.tableRepo.GetByID(ctx, id)
	if err != nil {
		return nil, err
	}

	return p.mapTableToResponse(table), nil
}

func (p *TableProcessor) Update(ctx context.Context, id uuid.UUID, req models.UpdateTableRequest) (*models.TableResponse, error) {
	table, err := p.tableRepo.GetByID(ctx, id)
	if err != nil {
		return nil, err
	}

	if req.TableName != nil {
		table.TableName = *req.TableName
	}
	if req.Status != nil {
		table.Status = string(*req.Status)
	}
	if req.PositionX != nil {
		table.PositionX = *req.PositionX
	}
	if req.PositionY != nil {
		table.PositionY = *req.PositionY
	}
	if req.Capacity != nil {
		table.Capacity = *req.Capacity
	}
	if req.IsActive != nil {
		table.IsActive = *req.IsActive
	}
	if req.Metadata != nil {
		table.Metadata = req.Metadata
	}

	err = p.tableRepo.Update(ctx, table)
	if err != nil {
		return nil, err
	}

	return p.mapTableToResponse(table), nil
}

func (p *TableProcessor) Delete(ctx context.Context, id uuid.UUID) error {
	table, err := p.tableRepo.GetByID(ctx, id)
	if err != nil {
		return err
	}

	if table.IsOccupied() {
		return errors.New("cannot delete occupied table")
	}

	return p.tableRepo.Delete(ctx, id)
}

func (p *TableProcessor) List(ctx context.Context, req models.ListTablesRequest) (*models.ListTablesResponse, error) {
	tables, total, err := p.tableRepo.List(ctx, req.OrganizationID, req.OutletID, (*string)(req.Status), req.IsActive, req.Search, req.Page, req.Limit)
	if err != nil {
		return nil, err
	}

	responses := make([]models.TableResponse, len(tables))
	for i, table := range tables {
		responses[i] = *p.mapTableToResponse(&table)
	}

	totalPages := int(math.Ceil(float64(total) / float64(req.Limit)))

	return &models.ListTablesResponse{
		Tables:     responses,
		TotalCount: int(total),
		Page:       req.Page,
		Limit:      req.Limit,
		TotalPages: totalPages,
	}, nil
}

func (p *TableProcessor) OccupyTable(ctx context.Context, tableID uuid.UUID, req models.OccupyTableRequest) (*models.TableResponse, error) {
	table, err := p.tableRepo.GetByID(ctx, tableID)
	if err != nil {
		return nil, err
	}

	if !table.CanBeOccupied() {
		return nil, errors.New("table is not available for occupation")
	}

	// Verify order exists
	_, err = p.orderRepo.GetByID(ctx, req.OrderID)
	if err != nil {
		return nil, errors.New("order not found")
	}

	err = p.tableRepo.OccupyTable(ctx, tableID, req.OrderID, &req.StartTime)
	if err != nil {
		return nil, err
	}

	// Get updated table
	updatedTable, err := p.tableRepo.GetByID(ctx, tableID)
	if err != nil {
		return nil, err
	}

	return p.mapTableToResponse(updatedTable), nil
}

func (p *TableProcessor) ReleaseTable(ctx context.Context, tableID uuid.UUID, req models.ReleaseTableRequest) (*models.TableResponse, error) {
	table, err := p.tableRepo.GetByID(ctx, tableID)
	if err != nil {
		return nil, err
	}

	if !table.IsOccupied() {
		return nil, errors.New("table is not occupied")
	}

	err = p.tableRepo.ReleaseTable(ctx, tableID, req.PaymentAmount)
	if err != nil {
		return nil, err
	}

	// Get updated table
	updatedTable, err := p.tableRepo.GetByID(ctx, tableID)
	if err != nil {
		return nil, err
	}

	return p.mapTableToResponse(updatedTable), nil
}

func (p *TableProcessor) GetAvailableTables(ctx context.Context, outletID uuid.UUID) ([]models.TableResponse, error) {
	tables, err := p.tableRepo.GetAvailableTables(ctx, outletID)
	if err != nil {
		return nil, err
	}

	responses := make([]models.TableResponse, len(tables))
	for i, table := range tables {
		responses[i] = *p.mapTableToResponse(&table)
	}

	return responses, nil
}

func (p *TableProcessor) GetOccupiedTables(ctx context.Context, outletID uuid.UUID) ([]models.TableResponse, error) {
	tables, err := p.tableRepo.GetOccupiedTables(ctx, outletID)
	if err != nil {
		return nil, err
	}

	responses := make([]models.TableResponse, len(tables))
	for i, table := range tables {
		responses[i] = *p.mapTableToResponse(&table)
	}

	return responses, nil
}

func (p *TableProcessor) mapTableToResponse(table *entities.Table) *models.TableResponse {
	response := &models.TableResponse{
		ID:             table.ID,
		OrganizationID: table.OrganizationID,
		OutletID:       table.OutletID,
		TableName:      table.TableName,
		StartTime:      table.StartTime,
		Status:         constants.TableStatus(table.Status),
		OrderID:        table.OrderID,
		PaymentAmount:  table.PaymentAmount,
		PositionX:      table.PositionX,
		PositionY:      table.PositionY,
		Capacity:       table.Capacity,
		IsActive:       table.IsActive,
		Metadata:       table.Metadata,
		CreatedAt:      table.CreatedAt,
		UpdatedAt:      table.UpdatedAt,
	}

	if table.Order != nil {
		response.Order = &models.OrderResponse{
			ID:             table.Order.ID,
			OrganizationID: table.Order.OrganizationID,
			OutletID:       table.Order.OutletID,
			UserID:         table.Order.UserID,
			CustomerID:     table.Order.CustomerID,
			OrderNumber:    table.Order.OrderNumber,
			TableNumber:    table.Order.TableNumber,
			OrderType:      constants.OrderType(table.Order.OrderType),
			Status:         constants.OrderStatus(table.Order.Status),
			Subtotal:       table.Order.Subtotal,
			TaxAmount:      table.Order.TaxAmount,
			DiscountAmount: table.Order.DiscountAmount,
			TotalAmount:    table.Order.TotalAmount,
			TotalCost:      table.Order.TotalCost,
			PaymentStatus:  constants.PaymentStatus(table.Order.PaymentStatus),
			RefundAmount:   table.Order.RefundAmount,
			IsVoid:         table.Order.IsVoid,
			IsRefund:       table.Order.IsRefund,
			VoidReason:     table.Order.VoidReason,
			VoidedAt:       table.Order.VoidedAt,
			VoidedBy:       table.Order.VoidedBy,
			RefundReason:   table.Order.RefundReason,
			RefundedAt:     table.Order.RefundedAt,
			RefundedBy:     table.Order.RefundedBy,
			Metadata:       table.Order.Metadata,
			CreatedAt:      table.Order.CreatedAt,
			UpdatedAt:      table.Order.UpdatedAt,
		}
	}

	return response
}
