package de.effigenix.domain.masterdata.customer;

import de.effigenix.domain.masterdata.article.ArticleId;
import de.effigenix.shared.common.Money;
import de.effigenix.shared.common.Result;

import java.time.LocalDate;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Optional;

/**
 * Entity representing a frame contract within a Customer aggregate.
 * No own lifecycle — belongs to Customer (only B2B).
 *
 * Invariants:
 * - validUntil >= validFrom
 * - No duplicate ArticleIds in lineItems
 * - At least one lineItem
 */
public class FrameContract {

    private final FrameContractId id;
    private final LocalDate validFrom;
    private final LocalDate validUntil;
    private final DeliveryRhythm deliveryRhythm;
    private final List<ContractLineItem> lineItems;

    private FrameContract(
            FrameContractId id,
            LocalDate validFrom,
            LocalDate validUntil,
            DeliveryRhythm deliveryRhythm,
            List<ContractLineItem> lineItems
    ) {
        this.id = id;
        this.validFrom = validFrom;
        this.validUntil = validUntil;
        this.deliveryRhythm = deliveryRhythm;
        this.lineItems = new ArrayList<>(lineItems);
    }

    public static Result<String, FrameContract> create(
            LocalDate validFrom,
            LocalDate validUntil,
            DeliveryRhythm deliveryRhythm,
            List<ContractLineItem> lineItems
    ) {
        if (validFrom != null && validUntil != null && validUntil.isBefore(validFrom)) {
            return Result.failure("validUntil must not be before validFrom");
        }
        if (lineItems == null || lineItems.isEmpty()) {
            return Result.failure("FrameContract must have at least one line item");
        }
        long distinctArticles = lineItems.stream()
                .map(ContractLineItem::articleId)
                .distinct()
                .count();
        if (distinctArticles != lineItems.size()) {
            return Result.failure("Duplicate ArticleIds in line items");
        }
        return Result.success(new FrameContract(FrameContractId.generate(), validFrom, validUntil, deliveryRhythm, lineItems));
    }

    public static FrameContract reconstitute(
            FrameContractId id,
            LocalDate validFrom,
            LocalDate validUntil,
            DeliveryRhythm deliveryRhythm,
            List<ContractLineItem> lineItems
    ) {
        return new FrameContract(id, validFrom, validUntil, deliveryRhythm, lineItems);
    }

    public boolean isActive() {
        var today = LocalDate.now();
        return (validFrom == null || !today.isBefore(validFrom))
                && (validUntil == null || !today.isAfter(validUntil));
    }

    public boolean isExpired() {
        return validUntil != null && LocalDate.now().isAfter(validUntil);
    }

    public Optional<ContractLineItem> findLineItem(ArticleId articleId) {
        return lineItems.stream()
                .filter(li -> li.articleId().equals(articleId))
                .findFirst();
    }

    public Optional<Money> getAgreedPrice(ArticleId articleId) {
        return findLineItem(articleId).map(ContractLineItem::agreedPrice);
    }

    public FrameContractId id() { return id; }
    public LocalDate validFrom() { return validFrom; }
    public LocalDate validUntil() { return validUntil; }
    public DeliveryRhythm deliveryRhythm() { return deliveryRhythm; }
    public List<ContractLineItem> lineItems() { return Collections.unmodifiableList(lineItems); }

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

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