package de.effigenix.domain.masterdata.supplier;

import de.effigenix.shared.common.Result;
import de.effigenix.shared.common.Address;
import de.effigenix.shared.common.ContactInfo;
import de.effigenix.shared.common.PaymentTerms;

import java.time.OffsetDateTime;
import java.time.ZoneOffset;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import static de.effigenix.shared.common.Result.*;

/**
 * Aggregate Root for Supplier.
 *
 * Invariants:
 * 1. Name und ContactInfo (phone) sind Pflicht
 * 2. Address und PaymentTerms sind optional
 * 3. No structurally identical certificates (record equality)
 * 4. Rating scores 1-5 (enforced by SupplierRating VO)
 */
public class Supplier {

    private final SupplierId id;
    private SupplierName name;
    private Address address;
    private ContactInfo contactInfo;
    private PaymentTerms paymentTerms;
    private final List<QualityCertificate> certificates;
    private SupplierRating rating;
    private SupplierStatus status;
    private final OffsetDateTime createdAt;
    private OffsetDateTime updatedAt;

    private Supplier(
            SupplierId id,
            SupplierName name,
            Address address,
            ContactInfo contactInfo,
            PaymentTerms paymentTerms,
            List<QualityCertificate> certificates,
            SupplierRating rating,
            SupplierStatus status,
            OffsetDateTime createdAt,
            OffsetDateTime updatedAt
    ) {
        this.id = id;
        this.name = name;
        this.address = address;
        this.contactInfo = contactInfo;
        this.paymentTerms = paymentTerms;
        this.certificates = new ArrayList<>(certificates);
        this.rating = rating;
        this.status = status;
        this.createdAt = createdAt;
        this.updatedAt = updatedAt;
    }

    /**
     * Factory: Erzeugt einen neuen Supplier aus rohen Eingaben.
     * Orchestriert Validierung aller VOs intern.
     * Pflicht: name, phone. Optional: alles andere.
     */
    public static Result<SupplierError, Supplier> create(SupplierDraft draft) {
        // 1. Name validieren (Pflicht)
        SupplierName name;
        switch (SupplierName.of(draft.name())) {
            case Failure(var msg) -> { return Result.failure(new SupplierError.ValidationFailure(msg)); }
            case Success(var val) -> name = val;
        }

        // 2. ContactInfo validieren (Pflicht: phone; optional: email, contactPerson)
        ContactInfo contactInfo;
        switch (ContactInfo.create(draft.phone(), draft.email(), draft.contactPerson())) {
            case Failure(var msg) -> { return Result.failure(new SupplierError.ValidationFailure(msg)); }
            case Success(var val) -> contactInfo = val;
        }

        // 3. Address optional – nur wenn mindestens street oder city gesetzt
        Address address = null;
        if (draft.street() != null || draft.city() != null) {
            switch (Address.create(draft.street(), draft.houseNumber(), draft.postalCode(), draft.city(), draft.country())) {
                case Failure(var msg) -> { return Result.failure(new SupplierError.ValidationFailure(msg)); }
                case Success(var val) -> address = val;
            }
        }

        // 4. PaymentTerms optional
        PaymentTerms paymentTerms = null;
        if (draft.paymentDueDays() != null) {
            switch (PaymentTerms.create(draft.paymentDueDays(), draft.paymentDescription())) {
                case Failure(var msg) -> { return Result.failure(new SupplierError.ValidationFailure(msg)); }
                case Success(var val) -> paymentTerms = val;
            }
        }

        var now = OffsetDateTime.now(ZoneOffset.UTC);
        return Result.success(new Supplier(
                SupplierId.generate(), name, address, contactInfo, paymentTerms,
                List.of(), null, SupplierStatus.ACTIVE, now, now
        ));
    }

    public static Supplier reconstitute(
            SupplierId id,
            SupplierName name,
            Address address,
            ContactInfo contactInfo,
            PaymentTerms paymentTerms,
            List<QualityCertificate> certificates,
            SupplierRating rating,
            SupplierStatus status,
            OffsetDateTime createdAt,
            OffsetDateTime updatedAt
    ) {
        return new Supplier(id, name, address, contactInfo, paymentTerms,
                certificates, rating, status, createdAt, updatedAt);
    }

    // ==================== Business Methods ====================

    /**
     * Wendet partielle Updates an. Das Aggregate validiert intern.
     * null-Felder im Draft werden ignoriert.
     */
    public Result<SupplierError, Void> update(SupplierUpdateDraft draft) {
        if (draft.name() != null) {
            switch (SupplierName.of(draft.name())) {
                case Failure(var msg) -> { return Result.failure(new SupplierError.ValidationFailure(msg)); }
                case Success(var val) -> this.name = val;
            }
        }

        if (draft.phone() != null) {
            switch (ContactInfo.create(draft.phone(), draft.email(), draft.contactPerson())) {
                case Failure(var msg) -> { return Result.failure(new SupplierError.ValidationFailure(msg)); }
                case Success(var val) -> this.contactInfo = val;
            }
        }

        if (draft.street() != null || draft.city() != null) {
            switch (Address.create(draft.street(), draft.houseNumber(), draft.postalCode(), draft.city(), draft.country())) {
                case Failure(var msg) -> { return Result.failure(new SupplierError.ValidationFailure(msg)); }
                case Success(var val) -> this.address = val;
            }
        }

        if (draft.paymentDueDays() != null) {
            switch (PaymentTerms.create(draft.paymentDueDays(), draft.paymentDescription())) {
                case Failure(var msg) -> { return Result.failure(new SupplierError.ValidationFailure(msg)); }
                case Success(var val) -> this.paymentTerms = val;
            }
        }

        touch();
        return Result.success(null);
    }

    public void addCertificate(QualityCertificate certificate) {
        if (!this.certificates.contains(certificate)) {
            this.certificates.add(certificate);
            touch();
        }
    }

    public void removeCertificate(QualityCertificate certificate) {
        this.certificates.remove(certificate);
        touch();
    }

    public void rate(SupplierRating newRating) {
        this.rating = newRating;
        touch();
    }

    public void activate() {
        this.status = SupplierStatus.ACTIVE;
        touch();
    }

    public void deactivate() {
        this.status = SupplierStatus.INACTIVE;
        touch();
    }

    public List<QualityCertificate> expiredCertificates() {
        return certificates.stream()
                .filter(QualityCertificate::isExpired)
                .toList();
    }

    public List<QualityCertificate> certificatesExpiringSoon(int days) {
        return certificates.stream()
                .filter(c -> c.expiresWithinDays(days))
                .toList();
    }

    // ==================== Helpers ====================

    private void touch() {
        this.updatedAt = OffsetDateTime.now(ZoneOffset.UTC);
    }

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

    public SupplierId id() { return id; }
    public SupplierName name() { return name; }
    public Address address() { return address; }
    public ContactInfo contactInfo() { return contactInfo; }
    public PaymentTerms paymentTerms() { return paymentTerms; }
    public List<QualityCertificate> certificates() { return Collections.unmodifiableList(certificates); }
    public SupplierRating rating() { return rating; }
    public SupplierStatus status() { return status; }
    public OffsetDateTime createdAt() { return createdAt; }
    public OffsetDateTime updatedAt() { return updatedAt; }

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

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

    @Override
    public String toString() {
        return "Supplier{id=" + id + ", name=" + name + ", status=" + status + "}";
    }
}
