package org.hso.ecommerce.action.shop;

import org.hso.ecommerce.action.booking.CreateBookingAction;
import org.hso.ecommerce.entities.booking.*;
import org.hso.ecommerce.entities.shop.Address;
import org.hso.ecommerce.entities.shop.Article;
import org.hso.ecommerce.entities.shop.CustomerOrder;
import org.hso.ecommerce.entities.shop.CustomerOrderPosition;
import org.hso.ecommerce.entities.user.User;
import org.hso.ecommerce.entities.warehouse.WarehouseBooking;
import org.hso.ecommerce.entities.warehouse.WarehouseBookingPosition;
import org.hso.ecommerce.entities.warehouse.WarehouseBookingPositionSlotEntry;

import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

public class CreateOrderAction {

    private User user;
    Address destination;

    private int expectedTotalGrossCent;

    private int totalNetCent;
    private int totalVatCent;
    private PaymentMethod method;

    private BookingAccountEntry latestUserBooking;
    private BookingAccountEntry latestVatBooking;
    private BookingAccountEntry latestMainBooking;

    private List<OrderItem> orderItems = new ArrayList<>();

    public CreateOrderAction(
            User user,
            int expectedTotalGrossCent,
            Address destination,
            PaymentMethod method,
            BookingAccountEntry latestUserBooking,
            BookingAccountEntry latestVatBooking,
            BookingAccountEntry latestMainBooking
    ) {
        this.user = user;
        this.expectedTotalGrossCent = expectedTotalGrossCent;
        this.destination = destination;
        this.method = method;

        this.latestUserBooking = latestUserBooking;
        assert latestUserBooking.userAccount.id == user.id;

        this.latestVatBooking = latestVatBooking;
        assert latestVatBooking.isVATAccount;

        this.latestMainBooking = latestMainBooking;
        assert latestMainBooking.isMainAccount;
    }

    public void addArticle(Article article, int quantity, List<WarehouseBookingPositionSlotEntry> availableSlots) {
        for (WarehouseBookingPositionSlotEntry slot : availableSlots) {
            assert slot.article.id == article.id;
        }

        orderItems.add(new OrderItem(article, availableSlots, quantity));

        totalNetCent += article.shopPricePerUnitNetCent * quantity;
        totalVatCent += article.getVat() * quantity;
    }


    public Result finish() throws ArticleNotInStockException {
        CustomerOrder order = createOrder();
        CustomerPayment payment = createPayment();

        List<Booking> bookingList = new ArrayList<>();
        bookingList.add(new CreateBookingAction(latestUserBooking, latestMainBooking, new BookingReason(order), order.totalGrossCent).finish());
        bookingList.add(new CreateBookingAction(null, latestUserBooking, new BookingReason(payment), order.totalGrossCent).finish());
        bookingList.add(new CreateBookingAction(latestMainBooking, latestVatBooking, new BookingReason(order), order.totalVatCent).finish());

        WarehouseBooking warehouseBooking = createWarehouseBooking(order);

        return new Result(
                order,
                warehouseBooking,
                bookingList
        );
    }

    private WarehouseBooking createWarehouseBooking(CustomerOrder order) throws ArticleNotInStockException {
        WarehouseBooking booking = new WarehouseBooking();
        booking.created = new Timestamp(new Date().getTime());
        booking.reason = new BookingReason(order);

        for (OrderItem item : orderItems) {
            int needed = item.quantity;

            for (WarehouseBookingPositionSlotEntry slot : item.availableSlots) {
                int remove = Math.min(slot.newSumSlot, needed);
                needed -= remove;

                WarehouseBookingPosition bookingPosition = new WarehouseBookingPosition();

                bookingPosition.article = item.article;
                bookingPosition.amount = -remove;
                bookingPosition.slotEntry = slot.copyAddAmount(-remove);
                bookingPosition.booking = booking;

                booking.positions.add(bookingPosition);

                if (needed == 0) {
                    break;
                }
            }

            if (needed > 0) {
                throw new ArticleNotInStockException(item.article);
            }
        }

        return booking;
    }

    private CustomerPayment createPayment() {
        CustomerPayment payment = new CustomerPayment();
        payment.amountCent = totalNetCent + totalVatCent;
        payment.payment = method;
        return payment;
    }


    private CustomerOrder createOrder() {
        assert totalNetCent + totalVatCent == expectedTotalGrossCent;

        CustomerOrder customerOrder = new CustomerOrder();
        customerOrder.customer = user;
        customerOrder.destination = destination;

        for (OrderItem item : orderItems) {
            CustomerOrderPosition position = new CustomerOrderPosition();
            position.article = item.article;
            position.pricePerUnit = item.article.shopPricePerUnitNetCent;
            position.quantity = item.quantity;

            position.order = customerOrder;

            customerOrder.positions.add(position);
        }

        customerOrder.created = new Timestamp(new Date().getTime());

        customerOrder.totalNetCent = totalNetCent;
        customerOrder.totalVatCent = totalVatCent;
        customerOrder.totalGrossCent = totalNetCent + totalVatCent;

        return customerOrder;
    }

    public static class Result {
        public final CustomerOrder customerOrder;
        public final WarehouseBooking warehouseBooking;
        public final List<Booking> bookings;

        Result(CustomerOrder customerOrder, WarehouseBooking warehouseBooking, List<Booking> bookings) {
            this.customerOrder = customerOrder;
            this.warehouseBooking = warehouseBooking;
            this.bookings = bookings;
        }
    }

    private static class OrderItem {
        List<WarehouseBookingPositionSlotEntry> availableSlots;
        int quantity;
        Article article;

        public OrderItem(Article article, List<WarehouseBookingPositionSlotEntry> availableSlots, int quantity) {
            this.article = article;
            this.availableSlots = availableSlots;
            this.quantity = quantity;
        }
    }

    public static class ArticleNotInStockException extends Exception {
        private Article article;

        public ArticleNotInStockException(Article article) {
            super("The quantity of article '" + article.title + "' is not in stock.");
            this.article = article;
        }

        public Article getArticle() {
            return article;
        }
    }
}
