package org.hso.ecommerce.controller.intern.accounting;

import java.util.Optional;

import org.hso.ecommerce.action.booking.CreateBookingAction;
import org.hso.ecommerce.entities.booking.Booking;
import org.hso.ecommerce.entities.booking.BookingAccountEntry;
import org.hso.ecommerce.entities.booking.BookingReason;
import org.hso.ecommerce.repos.booking.BookingAccountEntryRepository;
import org.hso.ecommerce.repos.booking.BookingRepository;
import org.hso.ecommerce.repos.supplier.SupplierRepository;
import org.hso.ecommerce.repos.user.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping;

@Controller
public class ManualAccountingController {

    @Autowired
    final BookingRepository bookingRepository = null;

    @Autowired
    final BookingAccountEntryRepository bookingAccountEntryRepository = null;

    @Autowired
    final UserRepository userRepository = null;

    @Autowired
    final SupplierRepository supplierRepository = null;

    /**
     * Collection for the form values used to create a manual booking
     */
    public static class ManualAccounting {
        String amount;
        String source;
        String sourceCustomer;
        String sourceSupplier;
        String destination;
        String destinationCustomer;
        String destinationSupplier;
        String reason;
        String reasonText;

        /**
         * Default constructor with default values for the form
         */
        public ManualAccounting() {
            amount = "0.00";
            reason = "Manual";
        }

        public String getAmount() {
            return amount;
        }

        public void setAmount(String amount) {
            this.amount = amount;
        }

        public String getSource() {
            return source;
        }

        public void setSource(String source) {
            this.source = source;
        }

        public String getSourceCustomer() {
            return sourceCustomer;
        }

        public void setSourceCustomer(String sourceCustomer) {
            this.sourceCustomer = sourceCustomer;
        }

        public String getSourceSupplier() {
            return sourceSupplier;
        }

        public void setSourceSupplier(String sourceSupplier) {
            this.sourceSupplier = sourceSupplier;
        }

        public String getDestination() {
            return destination;
        }

        public void setDestination(String destination) {
            this.destination = destination;
        }

        public String getDestinationCustomer() {
            return destinationCustomer;
        }

        public void setDestinationCustomer(String destinationCustomer) {
            this.destinationCustomer = destinationCustomer;
        }

        public String getDestinationSupplier() {
            return destinationSupplier;
        }

        public void setDestinationSupplier(String destinationSupplier) {
            this.destinationSupplier = destinationSupplier;
        }

        public String getReason() {
            return reason;
        }

        public void setReason(String reason) {
            this.reason = reason;
        }

        public String getReasonText() {
            return reasonText;
        }

        public void setReasonText(String reasonText) {
            this.reasonText = reasonText;
        }
    }

    /**
     * An exception to represent errors that can be shown to the user
     */
    public static class InvalidFormDataException extends Exception {
        public InvalidFormDataException(String msg) {
            super(msg);
        }

        private static final long serialVersionUID = 1L;
    }

    private boolean sameAccountBothSides(ManualAccounting formData) {
        // No need to check for NumberFormatException because the numbers were already parsed before.
        if (!formData.getSource().equals(formData.getDestination())) {
            return false;
        } else if (formData.getSource().equals("Cust")
                && Long.parseLong(formData.getSourceCustomer()) != Long.parseLong(formData.getDestinationCustomer())) {
            return false;
        } else if (formData.getSource().equals("Sup")
                && Long.parseLong(formData.getSourceSupplier()) != Long.parseLong(formData.getDestinationSupplier())) {
            return false;
        } else {
            return true;
        }
    }

    public Booking createBooking(ManualAccounting formData) throws InvalidFormDataException {
        if (formData.getSource() == null) {
            throw new InvalidFormDataException("Bitte wählen Sie ein Quellkonto aus.");
        } else if (formData.getDestination() == null) {
            throw new InvalidFormDataException("Bitte wählen Sie ein Zielkonto aus.");
        }
        BookingAccountEntry source = getAccountFromFormData(formData.getSource(), formData.getSourceCustomer(),
                formData.getSourceSupplier());
        BookingAccountEntry destination = getAccountFromFormData(formData.getDestination(),
                formData.getDestinationCustomer(), formData.getDestinationSupplier());

        if (sameAccountBothSides(formData)) {
            throw new InvalidFormDataException("Quell- und Zielkonto dürfen nicht das selbe sein.");
        }

        double doubleAmount;
        try {
            doubleAmount = Double.parseDouble(formData.amount);
        } catch (NumberFormatException e) {
            throw new InvalidFormDataException("Der angegebene Betrag ist ungültig.");
        }
        int amountCent = (int) Math.round(doubleAmount * 100);

        BookingReason reason = new BookingReason();
        if (formData.getReason().equals("Start")) {
            reason.isStartBooking = true;
        } else if (formData.getReason().equals("Manual")) {
            reason.isManuel = true;
        } else {
            throw new RuntimeException("Invalid form value for the booking reason: " + formData.getReason());
        }
        reason.comment = formData.reasonText;

        CreateBookingAction action = new CreateBookingAction(source, destination, reason, amountCent);
        return action.finish();
    }

    /**
     * Retrieve the corresponding account based on user-input
     *
     * @param account  The chosen value on the radio buttons
     * @param customer The given customer id from the corresponding text field
     * @param supplier the given supplier id from the corresponding text field
     * @return The account entry that can be used to create the new booking
     * @throws InvalidFormDataException If the user provided incorrect data in any way
     */
    private BookingAccountEntry getAccountFromFormData(String account, String customer, String supplier)
            throws InvalidFormDataException {
        if (account.equals("None")) {
            return null;
        } else if (account.equals("Main")) {
            return bookingAccountEntryRepository.getByMain().orElseGet(BookingAccountEntry::newMain);
        } else if (account.equals("Vat")) {
            return bookingAccountEntryRepository.getByVat().orElseGet(BookingAccountEntry::newVat);
        } else if (account.equals("Cust")) {
            long userId;
            try {
                userId = Long.parseLong(customer);
            } catch (NumberFormatException e) {
                throw new InvalidFormDataException("Die angegebene Kunden-Nr. ist ungültig.");
            }
            Optional<BookingAccountEntry> bookingAccount = bookingAccountEntryRepository.getByUser(userId);
            if (!bookingAccount.isPresent()) {
                bookingAccount = userRepository.findById(userId).map((user) -> BookingAccountEntry.newUser(user));
            }
            if (bookingAccount.isPresent()) {
                return bookingAccount.get();
            } else {
                throw new InvalidFormDataException("Der Kunde Nr. " + userId + " konnte nicht gefunden werden.");
            }
        } else if (account.equals("Sup")) {
            long supplierId;
            try {
                supplierId = Long.parseLong(supplier);
            } catch (NumberFormatException e) {
                throw new InvalidFormDataException("Die angegebene Lieferanten-Nr. ist ungültig.");
            }
            Optional<BookingAccountEntry> bookingAccount = bookingAccountEntryRepository.getBySupplier(supplierId);
            if (!bookingAccount.isPresent()) {
                bookingAccount = supplierRepository.findById(supplierId)
                        .map((sup) -> BookingAccountEntry.newSupplier(sup));
            }
            if (bookingAccount.isPresent()) {
                return bookingAccount.get();
            } else {
                throw new InvalidFormDataException(
                        "Der Lieferant Nr. " + supplierId + " konnte nicht gefunden werden.");
            }
        } else {
            throw new RuntimeException("Invalid form value for an account: " + account);
        }
    }

    @GetMapping("/intern/accounting/addManual")
    public String accountingAddManual(Model model) {
        model.addAttribute("form_vals", new ManualAccounting());
        return "intern/accounting/addManual";
    }

    @PostMapping("/intern/accounting/addManual")
    public String accountingAddManualSubmit(Model model, @ModelAttribute ManualAccounting formData) {
        Booking booking;
        try {
            booking = createBooking(formData);
            bookingRepository.save(booking);
            model.addAttribute("info", "Die Buchung wurde erfolgreich erstellt.");
            model.addAttribute("form_vals", new ManualAccounting()); // Reply with empty form on success
        } catch (InvalidFormDataException e) {
            model.addAttribute("error", e.getMessage());
            model.addAttribute("form_vals", formData);
        }
        return "intern/accounting/addManual";
    }

}
