package de.effigenix.application.inventory;

import de.effigenix.domain.inventory.InventoryAction;
import de.effigenix.domain.inventory.MovementType;
import de.effigenix.domain.inventory.StockId;
import de.effigenix.domain.inventory.StockMovement;
import de.effigenix.domain.inventory.StockMovementError;
import de.effigenix.domain.inventory.StockMovementRepository;
import de.effigenix.domain.masterdata.article.ArticleId;
import de.effigenix.shared.common.RepositoryError;
import de.effigenix.shared.common.Result;
import de.effigenix.shared.security.ActorId;
import de.effigenix.shared.security.AuthorizationPort;

import java.time.Instant;
import java.util.List;

public class ListStockMovements {

    private final StockMovementRepository stockMovementRepository;
    private final AuthorizationPort authPort;

    public ListStockMovements(StockMovementRepository stockMovementRepository, AuthorizationPort authPort) {
        this.stockMovementRepository = stockMovementRepository;
        this.authPort = authPort;
    }

    /**
     * Lists stock movements with optional filtering.
     * Filter priority (only one filter applied): stockId > articleId > batchReference > movementType > from/to.
     * If multiple filters are provided, only the highest-priority filter is used.
     */
    public Result<StockMovementError, List<StockMovement>> execute(
            String stockId, String articleId, String movementType,
            String batchReference, Instant from, Instant to,
            ActorId performedBy) {

        if (!authPort.can(performedBy, InventoryAction.STOCK_MOVEMENT_READ)) {
            return Result.failure(new StockMovementError.Unauthorized("Not authorized to list stock movements"));
        }

        if (stockId != null) {
            StockId sid;
            try {
                sid = StockId.of(stockId);
            } catch (IllegalArgumentException e) {
                return Result.failure(new StockMovementError.InvalidStockId(e.getMessage()));
            }
            return mapResult(stockMovementRepository.findAllByStockId(sid));
        }

        if (articleId != null) {
            ArticleId aid;
            try {
                aid = ArticleId.of(articleId);
            } catch (IllegalArgumentException e) {
                return Result.failure(new StockMovementError.InvalidArticleId(e.getMessage()));
            }
            return mapResult(stockMovementRepository.findAllByArticleId(aid));
        }

        if (batchReference != null) {
            if (batchReference.isBlank()) {
                return Result.failure(new StockMovementError.InvalidBatchReference(
                        "Batch reference must not be blank"));
            }
            return mapResult(stockMovementRepository.findAllByBatchReference(batchReference));
        }

        if (movementType != null) {
            MovementType type;
            try {
                type = MovementType.valueOf(movementType);
            } catch (IllegalArgumentException e) {
                return Result.failure(new StockMovementError.InvalidMovementType(
                        "Invalid movement type: " + movementType));
            }
            return mapResult(stockMovementRepository.findAllByMovementType(type));
        }

        if (from != null || to != null) {
            if (from != null && to != null && from.isAfter(to)) {
                return Result.failure(new StockMovementError.InvalidDateRange(
                        "'from' must not be after 'to'"));
            }
            if (from != null && to != null) {
                return mapResult(stockMovementRepository.findAllByPerformedAtBetween(from, to));
            }
            if (from != null) {
                return mapResult(stockMovementRepository.findAllByPerformedAtAfter(from));
            }
            return mapResult(stockMovementRepository.findAllByPerformedAtBefore(to));
        }

        return mapResult(stockMovementRepository.findAll());
    }

    private Result<StockMovementError, List<StockMovement>> mapResult(
            Result<RepositoryError, List<StockMovement>> result) {
        return switch (result) {
            case Result.Failure(var err) -> Result.failure(new StockMovementError.RepositoryFailure(err.message()));
            case Result.Success(var movements) -> Result.success(movements);
        };
    }
}
