package de.effigenix.domain.inventory;

import de.effigenix.shared.common.Quantity;
import de.effigenix.shared.common.Result;
import de.effigenix.shared.common.UnitOfMeasure;

import java.math.BigDecimal;
import java.time.Instant;
import java.time.LocalDate;
import java.time.format.DateTimeParseException;
import java.util.Objects;

/**
 * Child entity of Stock representing a single batch (Charge).
 *
 * Invariants:
 * - BatchReference (batchId + batchType) must be valid
 * - Quantity must be positive
 * - ExpiryDate must be a valid ISO date
 * - New batches always have status AVAILABLE
 * - receivedAt is set automatically on creation
 */
public class StockBatch {

    private final StockBatchId id;
    private final BatchReference batchReference;
    private final Quantity quantity;
    private final LocalDate expiryDate;
    private final StockBatchStatus status;
    private final Instant receivedAt;

    private StockBatch(StockBatchId id, BatchReference batchReference, Quantity quantity,
                       LocalDate expiryDate, StockBatchStatus status, Instant receivedAt) {
        this.id = id;
        this.batchReference = batchReference;
        this.quantity = quantity;
        this.expiryDate = expiryDate;
        this.status = status;
        this.receivedAt = receivedAt;
    }

    public static Result<StockError, StockBatch> create(StockBatchDraft draft) {
        // 1. BatchReference validieren
        BatchReference batchReference;
        switch (BatchReference.of(draft.batchId(), draft.batchType())) {
            case Result.Failure(var err) -> { return Result.failure(err); }
            case Result.Success(var val) -> batchReference = val;
        }

        // 2. Quantity validieren (Pflicht, positiv)
        Quantity quantity;
        try {
            BigDecimal amount = new BigDecimal(draft.quantityAmount());
            UnitOfMeasure uom;
            try {
                uom = UnitOfMeasure.valueOf(draft.quantityUnit());
            } catch (IllegalArgumentException | NullPointerException e) {
                return Result.failure(new StockError.InvalidQuantity("Invalid unit: " + draft.quantityUnit()));
            }
            switch (Quantity.of(amount, uom)) {
                case Result.Failure(var err) -> {
                    return Result.failure(new StockError.InvalidQuantity(err.message()));
                }
                case Result.Success(var val) -> quantity = val;
            }
        } catch (NumberFormatException | NullPointerException e) {
            return Result.failure(new StockError.InvalidQuantity(
                    "Invalid quantity amount: " + draft.quantityAmount()));
        }

        // 3. ExpiryDate validieren (Pflicht)
        LocalDate expiryDate;
        try {
            if (draft.expiryDate() == null || draft.expiryDate().isBlank()) {
                return Result.failure(new StockError.InvalidExpiryDate("expiryDate must not be blank"));
            }
            expiryDate = LocalDate.parse(draft.expiryDate());
        } catch (DateTimeParseException e) {
            return Result.failure(new StockError.InvalidExpiryDate(
                    "Invalid ISO date: " + draft.expiryDate()));
        }

        return Result.success(new StockBatch(
                StockBatchId.generate(), batchReference, quantity,
                expiryDate, StockBatchStatus.AVAILABLE, Instant.now()
        ));
    }

    public static StockBatch reconstitute(StockBatchId id, BatchReference batchReference,
                                           Quantity quantity, LocalDate expiryDate,
                                           StockBatchStatus status, Instant receivedAt) {
        return new StockBatch(id, batchReference, quantity, expiryDate, status, receivedAt);
    }

    // ==================== Removal ====================

    public Result<StockError, Quantity> removeQuantity(Quantity toRemove) {
        if (!isRemovable()) {
            return Result.failure(new StockError.BatchNotAvailable(id.value(), status.name()));
        }
        if (this.quantity.uom() != toRemove.uom()) {
            return Result.failure(new StockError.InvalidQuantity(
                    "Unit mismatch: batch has " + this.quantity.uom().symbol()
                            + ", removal requested " + toRemove.uom().symbol()));
        }
        BigDecimal remaining = this.quantity.amount().subtract(toRemove.amount());
        if (remaining.compareTo(BigDecimal.ZERO) < 0) {
            return Result.failure(new StockError.NegativeStockNotAllowed());
        }
        if (remaining.compareTo(BigDecimal.ZERO) == 0) {
            return Result.success(Quantity.reconstitute(BigDecimal.ZERO, this.quantity.uom()));
        }
        switch (Quantity.of(remaining, this.quantity.uom())) {
            case Result.Failure(var err) -> {
                return Result.failure(new StockError.InvalidQuantity(err.message()));
            }
            case Result.Success(var val) -> {
                return Result.success(val);
            }
        }
    }

    public boolean isRemovable() {
        return status == StockBatchStatus.AVAILABLE || status == StockBatchStatus.EXPIRING_SOON;
    }

    public StockBatch withQuantity(Quantity newQuantity) {
        return reconstitute(this.id, this.batchReference, newQuantity, this.expiryDate, this.status, this.receivedAt);
    }

    public StockBatch withStatus(StockBatchStatus newStatus) {
        return reconstitute(this.id, this.batchReference, this.quantity, this.expiryDate, newStatus, this.receivedAt);
    }

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

    public StockBatchId id() { return id; }
    public BatchReference batchReference() { return batchReference; }
    public Quantity quantity() { return quantity; }
    public LocalDate expiryDate() { return expiryDate; }
    public StockBatchStatus status() { return status; }
    public Instant receivedAt() { return receivedAt; }

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

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