package de.effigenix.application.inventory;

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

public class BookProductionOutput {

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

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

    public Result<ProductionOutputError, Void> execute(BookProductionOutputCommand cmd) {
        // 1. Find active PRODUCTION_AREA storage location
        StorageLocation productionArea;
        switch (storageLocationRepository.findByStorageType(StorageType.PRODUCTION_AREA)) {
            case Result.Failure(var err) -> {
                return Result.failure(new ProductionOutputError.RepositoryFailure(err.message()));
            }
            case Result.Success(var locations) -> {
                var active = locations.stream().filter(StorageLocation::active).findFirst();
                if (active.isEmpty()) {
                    return Result.failure(new ProductionOutputError.NoProductionArea(
                            "No active PRODUCTION_AREA storage location found"));
                }
                productionArea = active.get();
            }
        }

        var articleId = de.effigenix.domain.masterdata.article.ArticleId.of(cmd.articleId());
        var storageLocationId = productionArea.id();

        return unitOfWork.executeAtomically(() -> {
            // 2. Find or create Stock for (articleId, storageLocationId)
            Stock stock;
            switch (stockRepository.findByArticleIdAndStorageLocationId(articleId, storageLocationId)) {
                case Result.Failure(var err) -> {
                    return Result.failure(new ProductionOutputError.RepositoryFailure(err.message()));
                }
                case Result.Success(var opt) -> {
                    if (opt.isPresent()) {
                        stock = opt.get();
                    } else {
                        // Auto-create stock with no minimumLevel/minimumShelfLife
                        var stockDraft = new StockDraft(
                                cmd.articleId(), storageLocationId.value(), null, null, null);
                        switch (Stock.create(stockDraft)) {
                            case Result.Failure(var stockErr) -> {
                                return Result.failure(new ProductionOutputError.StockCreationFailed(
                                        stockErr.message()));
                            }
                            case Result.Success(var newStock) -> stock = newStock;
                        }
                        switch (stockRepository.save(stock)) {
                            case Result.Failure(var saveErr) -> {
                                return Result.failure(new ProductionOutputError.RepositoryFailure(
                                        saveErr.message()));
                            }
                            case Result.Success(var ignored) -> { }
                        }
                    }
                }
            }

            // 3. Add batch to stock
            StockBatch stockBatch;
            var batchDraft = new StockBatchDraft(
                    cmd.batchNumber(), "PRODUCED",
                    cmd.quantityAmount(), cmd.quantityUnit(),
                    cmd.bestBeforeDate());
            switch (stock.addBatch(batchDraft)) {
                case Result.Failure(var err) -> {
                    return Result.failure(new ProductionOutputError.BatchAdditionFailed(err.message()));
                }
                case Result.Success(var batch) -> stockBatch = batch;
            }

            // 4. Save stock with new batch
            switch (stockRepository.save(stock)) {
                case Result.Failure(var err) -> {
                    return Result.failure(new ProductionOutputError.RepositoryFailure(err.message()));
                }
                case Result.Success(var ignored) -> { }
            }

            // 5. Record stock movement
            var movementDraft = new StockMovementDraft(
                    stock.id().value(),
                    cmd.articleId(),
                    stockBatch.id().value(),
                    cmd.batchNumber(),
                    "PRODUCED",
                    "PRODUCTION_OUTPUT",
                    null,
                    cmd.quantityAmount(),
                    cmd.quantityUnit(),
                    null,
                    null,
                    "SYSTEM"
            );
            StockMovement movement;
            switch (StockMovement.record(movementDraft)) {
                case Result.Failure(var err) -> {
                    return Result.failure(new ProductionOutputError.MovementRecordingFailed(err.message()));
                }
                case Result.Success(var mv) -> movement = mv;
            }

            switch (stockMovementRepository.save(movement)) {
                case Result.Failure(var err) -> {
                    return Result.failure(new ProductionOutputError.RepositoryFailure(err.message()));
                }
                case Result.Success(var ignored) -> { }
            }

            return Result.success(null);
        });
    }
}
