package de.effigenix.application.inventory;

import de.effigenix.application.inventory.command.BookProductionConsumptionCommand;
import de.effigenix.domain.inventory.*;
import de.effigenix.domain.masterdata.article.ArticleId;
import de.effigenix.shared.common.Quantity;
import de.effigenix.shared.common.Result;
import de.effigenix.shared.common.UnitOfMeasure;
import de.effigenix.shared.persistence.UnitOfWork;

import java.math.BigDecimal;

public class BookProductionConsumption {

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

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

    public Result<ProductionConsumptionError, Void> execute(BookProductionConsumptionCommand cmd) {
        var articleId = ArticleId.of(cmd.articleId());

        // 1. Find all stocks for the article
        java.util.List<Stock> stocks;
        switch (stockRepository.findAllByArticleId(articleId)) {
            case Result.Failure(var err) -> {
                return Result.failure(new ProductionConsumptionError.RepositoryFailure(err.message()));
            }
            case Result.Success(var list) -> stocks = list;
        }

        if (stocks.isEmpty()) {
            return Result.failure(new ProductionConsumptionError.StockNotFound(
                    "No stock found for article " + cmd.articleId()));
        }

        // 2. Find the batch across all stocks by matching batchReference.batchId
        Stock targetStock = null;
        StockBatch targetBatch = null;
        for (Stock stock : stocks) {
            for (StockBatch batch : stock.batches()) {
                if (batch.batchReference().batchId().equals(cmd.inputBatchReferenceId())) {
                    targetStock = stock;
                    targetBatch = batch;
                    break;
                }
            }
            if (targetBatch != null) break;
        }

        if (targetBatch == null) {
            return Result.failure(new ProductionConsumptionError.BatchNotFoundInStock(
                    "Batch with reference " + cmd.inputBatchReferenceId() + " not found in any stock for article " + cmd.articleId()));
        }

        // 3. Parse quantity
        Quantity quantity;
        try {
            var amount = new BigDecimal(cmd.quantityAmount());
            var uom = UnitOfMeasure.valueOf(cmd.quantityUnit());
            switch (Quantity.of(amount, uom)) {
                case Result.Failure(var err) -> {
                    return Result.failure(new ProductionConsumptionError.RemovalFailed(err.message()));
                }
                case Result.Success(var val) -> quantity = val;
            }
        } catch (IllegalArgumentException e) {
            return Result.failure(new ProductionConsumptionError.RemovalFailed(
                    "Invalid quantity: " + cmd.quantityAmount() + " " + cmd.quantityUnit()));
        }

        final Stock stock = targetStock;
        final StockBatch batch = targetBatch;

        return unitOfWork.executeAtomically(() -> {
            // 4. Remove quantity from batch
            switch (stock.removeBatch(batch.id(), quantity)) {
                case Result.Failure(var err) -> {
                    return Result.failure(new ProductionConsumptionError.RemovalFailed(err.message()));
                }
                case Result.Success(var ignored) -> { }
            }

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

            // 6. Record stock movement
            var movementDraft = new StockMovementDraft(
                    stock.id().value(),
                    cmd.articleId(),
                    batch.id().value(),
                    batch.batchReference().batchId(),
                    batch.batchReference().batchType().name(),
                    "PRODUCTION_CONSUMPTION",
                    null,
                    cmd.quantityAmount(),
                    cmd.quantityUnit(),
                    null,
                    null,
                    "SYSTEM"
            );
            StockMovement movement;
            switch (StockMovement.record(movementDraft)) {
                case Result.Failure(var err) -> {
                    return Result.failure(new ProductionConsumptionError.MovementRecordingFailed(err.message()));
                }
                case Result.Success(var mv) -> movement = mv;
            }

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

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