package de.accso.flexinale.backoffice.api_in.rest;

import de.accso.flexinale.backoffice.api_out.event.KinoPublisher;
import de.accso.flexinale.backoffice.api_out.event.VorfuehrungPublisher;
import de.accso.flexinale.backoffice.application.services.KinoUploadService;
import de.accso.flexinale.backoffice.application.services.KinoSaalUploadService;
import de.accso.flexinale.backoffice.domain.model.Kino;
import de.accso.flexinale.backoffice.domain.model.KinoSaal;
import de.accso.flexinale.backoffice.domain.model.Vorfuehrung;
import de.accso.flexinale.backoffice.application.services.KinoService;
import de.accso.flexinale.backoffice.application.services.VorfuehrungService;
import de.accso.flexinale.common.application.PersistMode;
import de.accso.flexinale.common.application.PersistedEntityAndResult;
import de.accso.flexinale.common.shared_kernel.FlexinaleIllegalStateException;
import de.accso.flexinale.common.shared_kernel.Identifiable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Profile;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;

import java.io.BufferedInputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;

import static de.accso.flexinale.common.application.PersistedEntityAndResult.PersistedResult.ADDED;
import static de.accso.flexinale.common.application.PersistedEntityAndResult.PersistedResult.UPDATED;

@RestController
@Profile({ "!testdata-besucherportal & !testdata-ticketing" })
public class KinoKinoSaalRestController {
    private static final Logger LOGGER = LoggerFactory.getLogger(KinoKinoSaalRestController.class);

    @Autowired
    private KinoService kinoService;

    @Autowired
    private KinoUploadService kinoUploadService;

    @Autowired
    private KinoSaalUploadService kinoSaalUploadService;

    @Autowired
    private KinoPublisher kinoPublisher;

    @Autowired
    private VorfuehrungService vorfuehrungService;

    @Autowired
    private VorfuehrungPublisher vorfuehrungPublisher;

    @GetMapping(value = "/rest/kinos", produces = "application/json")
    @PreAuthorize("hasRole('ROLE_BESUCHER')")
    public List<Kino> kinos() {
        return kinoService.kinos();
    }

    @GetMapping(value = "/rest/kino/{id}", produces = "application/json")
    @PreAuthorize("hasRole('ROLE_BESUCHER')")
    public Kino kino(@PathVariable final String id) {
        Identifiable.Id kinoId = Identifiable.Id.of(id);
        Kino kino = kinoService.kino(kinoId);
        if (kino == null) {
            throw new RestResourceNotFoundException("no Kino %s found.".formatted(id));
        }

        return kino;
    }

    // -----------------------------------------------------------------------------------------------------------

    @PostMapping("/rest/kinos")
    @PreAuthorize("hasRole('ROLE_ADMIN')")
    public ResponseEntity<RestResponseMessage> uploadKinosKinoSaele(@RequestParam("file") final MultipartFile file) {
        String originalFilename = file.getOriginalFilename();

        try {
            Collection<KinoSaal> alleKinoSaele;

            // we need to open the stream twice, separately for KinoSaele ...
            try (BufferedInputStream stream = new BufferedInputStream(file.getInputStream())) {
                if (!ExcelHelper.isExcelFile(stream)) {
                    String message = "Please upload a valid Excel file!";
                    throw new FlexinaleIllegalStateException(message);
                }
                alleKinoSaele = loadKinoSaele(stream);
            }

            // ... and for Kino
            try (BufferedInputStream stream = new BufferedInputStream(file.getInputStream())) {
                return loadAndPersistAndPublishNewKinosKinoSaele(stream, originalFilename, alleKinoSaele);
            }
        }
        catch (Exception ex) {
            String message = "Could not upload the Excel file: %s: %s".formatted(file.getOriginalFilename(), ex.getMessage());
            RestBadRequestException exception = new RestBadRequestException(message, ex);
            LOGGER.error(message, exception);

            throw exception;
        }
    }

    Collection<KinoSaal> loadKinoSaele(final BufferedInputStream stream) throws IOException {
        return kinoSaalUploadService.loadDataFromExcelSheet(stream);
    }

    ResponseEntity<RestResponseMessage> loadAndPersistAndPublishNewKinosKinoSaele(
            final BufferedInputStream stream, final String originalFilename,
            final Collection<KinoSaal> alleKinoSaele) throws IOException {
        // and then load and persist all Kinos (including their KinoSaele)
        Map<Identifiable.Id, Collection<KinoSaal>> alleKinosUndIhreSaele =
                kinoSaalUploadService.mapKinoSaeleToKino(alleKinoSaele);

        List<Kino> addedKinos;
        List<Kino> updatedKinos;
        try {
            kinoUploadService.beforeLoad(alleKinosUndIhreSaele); // fix state of Kino-KinoSaal in map
            // load and persist
            Collection<PersistedEntityAndResult<Kino>> addedAndUpdatedKinos =
                    kinoUploadService.loadDataFromExcelSheetAndPersistAndReturn(stream, PersistMode.UPDATE);

            addedKinos = addedAndUpdatedKinos.stream()
                    .filter(kinoWithResult -> kinoWithResult.result().equals(ADDED))
                    .map(PersistedEntityAndResult::entity)
                    .toList();
            updatedKinos = addedAndUpdatedKinos.stream()
                    .filter(kinoWithResult -> kinoWithResult.result().equals(UPDATED))
                    .map(PersistedEntityAndResult::entity)
                    .toList();

            publishKinosAndVorfuehrungen(addedKinos, updatedKinos);
        }
        finally {
            kinoUploadService.afterLoad(); // clear map
        }

        String message = "Uploaded the Excel file %s adding %d new Kinos entities and updating %d existing Kinos entities."
                .formatted(originalFilename, addedKinos.size(), updatedKinos.size());

        RestUploadResult restUploadResult = new RestUploadResult(HttpStatus.OK, message);
        return ResponseEntity.status(restUploadResult.status())
                .body(new RestResponseMessage(restUploadResult.message()));
    }

    private void publishKinosAndVorfuehrungen(final List<Kino> addedKinos, final List<Kino> updatedKinos) {
        kinoPublisher.publishNewKinos(addedKinos);
        kinoPublisher.publishUpdatedKinos(updatedKinos);

        List<Vorfuehrung> vorfuehrungen = vorfuehrungService.vorfuehrungen();
        List<Vorfuehrung> vorfuehrungenToPublishAsUpdated = new ArrayList<>();
        for (Kino updatedKino: updatedKinos) {
            for (KinoSaal updatedKinoSaal: updatedKino.kinoSaele) {
                vorfuehrungen.stream()
                        .filter(vorfuehrung -> vorfuehrung.kinoSaal.id.equals(updatedKinoSaal.id))
                        .forEach(vorfuehrungenToPublishAsUpdated::add);
            }
        }
        vorfuehrungPublisher.publishUpdatedVorfuehrungen(vorfuehrungenToPublishAsUpdated);
    }
}