package de.effigenix.application.inventory;

import de.effigenix.application.inventory.command.ConfirmReservationCommand;
import de.effigenix.domain.inventory.*;
import de.effigenix.shared.common.Result;
import de.effigenix.shared.persistence.UnitOfWork;

import java.util.ArrayList;
import java.util.List;

public class ConfirmReservation {

    private final StockRepository stockRepository;
    private final StockMovementRepository stockMovementRepository;
    private final UnitOfWork unitOfWork;

    public ConfirmReservation(StockRepository stockRepository,
                              StockMovementRepository stockMovementRepository,
                              UnitOfWork unitOfWork) {
        this.stockRepository = stockRepository;
        this.stockMovementRepository = stockMovementRepository;
        this.unitOfWork = unitOfWork;
    }

    public Result<StockError, Void> execute(ConfirmReservationCommand cmd) {
        // 1. Stock laden
        Stock stock;
        switch (stockRepository.findById(StockId.of(cmd.stockId()))) {
            case Result.Failure(var err) ->
                    { return Result.failure(new StockError.RepositoryFailure(err.message())); }
            case Result.Success(var opt) -> {
                if (opt.isEmpty()) {
                    return Result.failure(new StockError.StockNotFound(cmd.stockId()));
                }
                stock = opt.get();
            }
        }

        // 2. Reservation bestätigen
        ConfirmedReservation confirmed;
        switch (stock.confirmReservation(ReservationId.of(cmd.reservationId()))) {
            case Result.Failure(var err) -> { return Result.failure(err); }
            case Result.Success(var val) -> confirmed = val;
        }

        // 3. MovementType aus ReferenceType ableiten
        String movementType = confirmed.referenceType().toMovementType().name();

        // 4. StockMovements erzeugen
        List<StockMovement> movements = new ArrayList<>();
        for (ConfirmedAllocation alloc : confirmed.allocations()) {
            var draft = new StockMovementDraft(
                    stock.id().value(),
                    stock.articleId().value(),
                    alloc.stockBatchId().value(),
                    alloc.batchReference().batchId(),
                    alloc.batchReference().batchType().name(),
                    movementType,
                    null,
                    alloc.allocatedQuantity().amount().toPlainString(),
                    alloc.allocatedQuantity().uom().name(),
                    null,
                    null,
                    cmd.performedBy()
            );
            switch (StockMovement.record(draft)) {
                case Result.Failure(var err) ->
                        { return Result.failure(new StockError.MovementCreationFailed(err.message())); }
                case Result.Success(var val) -> movements.add(val);
            }
        }

        // 5. Atomar speichern
        return unitOfWork.executeAtomically(() -> {
            switch (stockRepository.save(stock)) {
                case Result.Failure(var err) ->
                        { return Result.failure(new StockError.RepositoryFailure(err.message())); }
                case Result.Success(var ignored) -> { }
            }
            for (StockMovement movement : movements) {
                switch (stockMovementRepository.save(movement)) {
                    case Result.Failure(var err) ->
                            { return Result.failure(new StockError.RepositoryFailure(err.message())); }
                    case Result.Success(var ignored) -> { }
                }
            }
            return Result.success(null);
        });
    }
}
