package de.effigenix.application.inventory;

import de.effigenix.application.inventory.command.CreateInventoryCountCommand;
import de.effigenix.domain.inventory.*;
import de.effigenix.shared.common.Result;
import de.effigenix.shared.persistence.UnitOfWork;
import de.effigenix.shared.security.ActorId;
import de.effigenix.shared.security.AuthorizationPort;

public class CreateInventoryCount {

    private final InventoryCountRepository inventoryCountRepository;
    private final StockRepository stockRepository;
    private final UnitOfWork unitOfWork;
    private final AuthorizationPort authPort;

    public CreateInventoryCount(InventoryCountRepository inventoryCountRepository,
                                 StockRepository stockRepository,
                                 UnitOfWork unitOfWork,
                                 AuthorizationPort authPort) {
        this.inventoryCountRepository = inventoryCountRepository;
        this.stockRepository = stockRepository;
        this.unitOfWork = unitOfWork;
        this.authPort = authPort;
    }

    public Result<InventoryCountError, InventoryCount> execute(CreateInventoryCountCommand cmd, ActorId actorId) {
        if (!authPort.can(actorId, InventoryAction.INVENTORY_COUNT_WRITE)) {
            return Result.failure(new InventoryCountError.Unauthorized("Not authorized to create inventory counts"));
        }

        // 1. Draft aus Command bauen
        var draft = new InventoryCountDraft(cmd.storageLocationId(), cmd.countDate(), cmd.initiatedBy());

        // 2. Aggregate erzeugen (validiert intern)
        InventoryCount count;
        switch (InventoryCount.create(draft)) {
            case Result.Failure(var err) -> { return Result.failure(err); }
            case Result.Success(var val) -> count = val;
        }

        // 3. Uniqueness-Check: max. eine aktive Inventur pro StorageLocation
        switch (inventoryCountRepository.existsActiveByStorageLocationId(count.storageLocationId())) {
            case Result.Failure(var err) ->
                    { return Result.failure(new InventoryCountError.RepositoryFailure(err.message())); }
            case Result.Success(var exists) -> {
                if (exists) {
                    return Result.failure(new InventoryCountError.ActiveCountExists(cmd.storageLocationId()));
                }
            }
        }

        // 4. Auto-Populate: Stocks für StorageLocation laden
        var stocksResult = stockRepository.findAllByStorageLocationId(count.storageLocationId());
        switch (stocksResult) {
            case Result.Failure(var err) ->
                    { return Result.failure(new InventoryCountError.RepositoryFailure(err.message())); }
            case Result.Success(var stocks) -> {
                for (Stock stock : stocks) {
                    // Skip stocks without batches – nothing to count
                    if (stock.batches().isEmpty()) {
                        continue;
                    }

                    var totalAmount = stock.batches().stream()
                            .map(b -> b.quantity().amount())
                            .reduce(java.math.BigDecimal.ZERO, java.math.BigDecimal::add);

                    String unit = stock.batches().getFirst().quantity().uom().name();

                    var itemDraft = new CountItemDraft(
                            stock.articleId().value(),
                            totalAmount.toPlainString(),
                            unit
                    );

                    switch (count.addCountItem(itemDraft)) {
                        case Result.Failure(var err) -> { return Result.failure(err); }
                        case Result.Success(var ignored) -> { }
                    }
                }
            }
        }

        // 5. Atomar speichern
        return unitOfWork.executeAtomically(() -> {
            switch (inventoryCountRepository.save(count)) {
                case Result.Failure(var err) ->
                        { return Result.failure(new InventoryCountError.RepositoryFailure(err.message())); }
                case Result.Success(var ignored) -> { }
            }
            return Result.success(count);
        });
    }
}
