/**
 * This file is part of alf.io.
 *
 * alf.io is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * alf.io is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with alf.io.  If not, see <http://www.gnu.org/licenses/>.
 */
package alfio.controller.api;

import alfio.controller.api.support.TicketHelper;
import alfio.controller.form.PaymentForm;
import alfio.controller.form.UpdateTicketOwnerForm;
import alfio.manager.EuVatChecker;
import alfio.manager.TicketReservationManager;
import alfio.manager.i18n.I18nManager;
import alfio.model.*;
import alfio.model.result.ValidationResult;
import alfio.model.transaction.PaymentProxy;
import alfio.repository.EventRepository;
import alfio.repository.TicketReservationRepository;
import alfio.util.Json;
import alfio.util.TemplateManager;
import lombok.AllArgsConstructor;
import org.apache.commons.lang3.tuple.Pair;
import org.apache.commons.lang3.tuple.Triple;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.support.RequestContextUtils;

import javax.servlet.http.HttpServletRequest;
import java.util.*;
import java.util.stream.Collectors;

import static alfio.model.PriceContainer.VatStatus.*;
import static org.apache.commons.lang3.StringUtils.isNotEmpty;

@RestController
@AllArgsConstructor
public class ReservationApiController {

    private final EventRepository eventRepository;
    private final TicketHelper ticketHelper;
    private final TemplateManager templateManager;
    private final I18nManager i18nManager;
    private final EuVatChecker vatChecker;
    private final TicketReservationRepository ticketReservationRepository;
    private final TicketReservationManager ticketReservationManager;


    @RequestMapping(value = "/event/{eventName}/ticket/{ticketIdentifier}/assign", method = RequestMethod.POST, headers = "X-Requested-With=XMLHttpRequest")
    public Map<String, Object> assignTicketToPerson(@PathVariable("eventName") String eventName,
                                                    @PathVariable("ticketIdentifier") String ticketIdentifier,
                                                    @RequestParam(value = "single-ticket", required = false, defaultValue = "false") boolean singleTicket,
                                                    UpdateTicketOwnerForm updateTicketOwner,
                                                    BindingResult bindingResult,
                                                    HttpServletRequest request,
                                                    Model model,
                                                    Authentication authentication) throws Exception {

        Optional<UserDetails> userDetails = Optional.ofNullable(authentication)
                .map(Authentication::getPrincipal)
                .filter(UserDetails.class::isInstance)
                .map(UserDetails.class::cast);

        Optional<Triple<ValidationResult, Event, Ticket>> assignmentResult = ticketHelper.assignTicket(eventName, ticketIdentifier, updateTicketOwner, Optional.of(bindingResult), request, t -> {
            Locale requestLocale = RequestContextUtils.getLocale(request);
            model.addAttribute("ticketFieldConfiguration", ticketHelper.findTicketFieldConfigurationAndValue(t.getMiddle().getId(), t.getRight(), requestLocale));
            model.addAttribute("value", t.getRight());
            model.addAttribute("validationResult", t.getLeft());
            model.addAttribute("countries", TicketHelper.getLocalizedCountries(requestLocale));
            model.addAttribute("event", t.getMiddle());
            model.addAttribute("useFirstAndLastName", t.getMiddle().mustUseFirstAndLastName());
            model.addAttribute("availableLanguages", i18nManager.getEventLanguages(eventName).stream()
                    .map(ContentLanguage.toLanguage(requestLocale)).collect(Collectors.toList()));
            String uuid = t.getRight().getUuid();
            model.addAttribute("urlSuffix", singleTicket ? "ticket/"+uuid+"/view": uuid);
            model.addAttribute("elementNamePrefix", "");
        }, userDetails);
        Map<String, Object> result = new HashMap<>();

        Optional<ValidationResult> validationResult = assignmentResult.map(Triple::getLeft);
        if(validationResult.isPresent() && validationResult.get().isSuccess()) {
            result.put("partial", templateManager.renderServletContextResource("/WEB-INF/templates/event/assign-ticket-result.ms", model.asMap(), request, TemplateManager.TemplateOutput.HTML));
        }
        result.put("validationResult", validationResult.orElse(ValidationResult.failed(new ValidationResult.ErrorDescriptor("fullName", "error.fullname"))));
        return result;
    }

    @RequestMapping(value = "/event/{eventName}/reservation/{reservationId}/vat-validation", method = RequestMethod.POST)
    @Transactional
    public ResponseEntity<VatDetail> validateEUVat(@PathVariable("eventName") String eventName,
                                                   @PathVariable("reservationId") String reservationId,
                                                   PaymentForm paymentForm,
                                                   Locale locale,
                                                   HttpServletRequest request) {

        String country = paymentForm.getVatCountryCode();
        Optional<Triple<Event, TicketReservation, VatDetail>> vatDetail = eventRepository.findOptionalByShortName(eventName)
            .flatMap(e -> ticketReservationRepository.findOptionalReservationById(reservationId).map(r -> Pair.of(e, r)))
            .filter(e -> EnumSet.of(INCLUDED, NOT_INCLUDED).contains(e.getKey().getVatStatus()))
            .filter(e -> vatChecker.isVatCheckingEnabledFor(e.getKey().getOrganizationId()))
            .flatMap(e -> vatChecker.checkVat(paymentForm.getVatNr(), country, e.getKey().getOrganizationId()).map(vd -> Triple.of(e.getLeft(), e.getRight(), vd)));

        vatDetail
            .filter(t -> t.getRight().isValid())
            .ifPresent(t -> {
                VatDetail vd = t.getRight();
                String billingAddress = vd.getName() + "\n" + vd.getAddress();
                PriceContainer.VatStatus vatStatus = determineVatStatus(t.getLeft().getVatStatus(), t.getRight().isVatExempt());
                ticketReservationRepository.updateBillingData(vatStatus, vd.getVatNr(), country, paymentForm.isInvoiceRequested(), reservationId);
                OrderSummary orderSummary = ticketReservationManager.orderSummaryForReservationId(reservationId, t.getLeft(), Locale.forLanguageTag(t.getMiddle().getUserLanguage()));
                ticketReservationRepository.addReservationInvoiceOrReceiptModel(reservationId, Json.toJson(orderSummary));
                ticketReservationRepository.updateTicketReservation(reservationId, t.getMiddle().getStatus().name(), paymentForm.getEmail(),
                    paymentForm.getFullName(), paymentForm.getFirstName(), paymentForm.getLastName(), locale.getLanguage(), billingAddress, null,
                    Optional.ofNullable(paymentForm.getPaymentMethod()).map(PaymentProxy::name).orElse(null));
                paymentForm.getTickets().forEach((ticketId, owner) -> {
                    if(isNotEmpty(owner.getEmail()) && ((isNotEmpty(owner.getFirstName()) && isNotEmpty(owner.getLastName())) || isNotEmpty(owner.getFullName()))) {
                        ticketHelper.preAssignTicket(eventName, reservationId, ticketId, owner, Optional.empty(), request, (tr) -> {}, Optional.empty());
                    }
                });
            });

        return vatDetail
            .map(Triple::getRight)
            .map(vd -> {
                if(vd.isValid()) {
                    return ResponseEntity.ok(vd);
                } else {
                    return new ResponseEntity<VatDetail>(HttpStatus.BAD_REQUEST);
                }
            })
            .orElseGet(() -> new ResponseEntity<>(HttpStatus.NOT_FOUND));
    }

    private static PriceContainer.VatStatus determineVatStatus(PriceContainer.VatStatus current, boolean isVatExempt) {
        if(!isVatExempt) {
            return current;
        }
        return current == NOT_INCLUDED ? NOT_INCLUDED_EXEMPT : INCLUDED_EXEMPT;
    }
}
