package 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 java.math.BigDecimal;
import java.util.Objects;

/**
 * Child entity of InventoryCount representing a single count position.
 *
 * Invariants:
 * - ArticleId must be valid
 * - ExpectedQuantity must be non-negative (>= 0)
 * - ActualQuantity is nullable until counted
 * - Deviation = actualQuantity - expectedQuantity (computed, only when actualQuantity is set)
 */
public class CountItem {

    private final CountItemId id;
    private final ArticleId articleId;
    private final Quantity expectedQuantity;
    private Quantity actualQuantity;

    private CountItem(CountItemId id, ArticleId articleId, Quantity expectedQuantity, Quantity actualQuantity) {
        this.id = id;
        this.articleId = articleId;
        this.expectedQuantity = expectedQuantity;
        this.actualQuantity = actualQuantity;
    }

    public static Result<InventoryCountError, CountItem> create(CountItemDraft draft) {
        // 1. ArticleId validieren
        if (draft.articleId() == null || draft.articleId().isBlank()) {
            return Result.failure(new InventoryCountError.InvalidArticleId("must not be blank"));
        }
        ArticleId articleId;
        try {
            articleId = ArticleId.of(draft.articleId());
        } catch (IllegalArgumentException e) {
            return Result.failure(new InventoryCountError.InvalidArticleId(e.getMessage()));
        }

        // 2. ExpectedQuantity validieren (>= 0)
        Quantity expectedQuantity;
        try {
            BigDecimal amount = new BigDecimal(draft.expectedQuantityAmount());
            if (amount.compareTo(BigDecimal.ZERO) < 0) {
                return Result.failure(new InventoryCountError.InvalidQuantity("Expected quantity must be >= 0"));
            }
            UnitOfMeasure uom;
            try {
                uom = UnitOfMeasure.valueOf(draft.expectedQuantityUnit());
            } catch (IllegalArgumentException | NullPointerException e) {
                return Result.failure(new InventoryCountError.InvalidQuantity("Invalid unit: " + draft.expectedQuantityUnit()));
            }
            expectedQuantity = Quantity.reconstitute(amount, uom);
        } catch (NumberFormatException | NullPointerException e) {
            return Result.failure(new InventoryCountError.InvalidQuantity(
                    "Invalid quantity amount: " + draft.expectedQuantityAmount()));
        }

        return Result.success(new CountItem(
                CountItemId.generate(), articleId, expectedQuantity, null
        ));
    }

    public static CountItem reconstitute(CountItemId id, ArticleId articleId,
                                          Quantity expectedQuantity, Quantity actualQuantity) {
        return new CountItem(id, articleId, expectedQuantity, actualQuantity);
    }

    /**
     * Computed deviation: actualQuantity - expectedQuantity.
     * Returns null if actualQuantity has not been set yet or if UOMs are incompatible.
     *
     * Invariant: actualQuantity and expectedQuantity should always share the same UOM.
     * A UOM mismatch indicates a data integrity issue.
     */
    public BigDecimal deviation() {
        if (actualQuantity == null) {
            return null;
        }
        if (actualQuantity.uom() != expectedQuantity.uom()) {
            return null;
        }
        return actualQuantity.amount().subtract(expectedQuantity.amount());
    }

    void recordActualQuantity(Quantity actualQuantity) {
        this.actualQuantity = actualQuantity;
    }

    public boolean isCounted() {
        return actualQuantity != null;
    }

    // ==================== Getters ====================

    public CountItemId id() { return id; }
    public ArticleId articleId() { return articleId; }
    public Quantity expectedQuantity() { return expectedQuantity; }
    public Quantity actualQuantity() { return actualQuantity; }

    @Override
    public boolean equals(Object obj) {
        if (this == obj) return true;
        if (!(obj instanceof CountItem other)) return false;
        return Objects.equals(id, other.id);
    }

    @Override
    public int hashCode() {
        return Objects.hash(id);
    }
}
