package de.effigenix.domain.inventory;

import de.effigenix.shared.common.Result;

import java.math.BigDecimal;
import java.util.Objects;

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

/**
 * StorageLocation aggregate root.
 *
 * Invariants:
 * - Name must not be blank, max 100 chars
 * - Name must be unique (Application Layer, Repository-Concern)
 * - StorageType is immutable after creation
 * - TemperatureRange: minTemperature < maxTemperature, values in range -50°C to +80°C
 * - Cannot deactivate if Stock exists at this location (Application Layer)
 */
public class StorageLocation {

    private final StorageLocationId id;
    private StorageLocationName name;
    private final StorageType storageType;
    private TemperatureRange temperatureRange;
    private boolean active;

    private StorageLocation(
            StorageLocationId id,
            StorageLocationName name,
            StorageType storageType,
            TemperatureRange temperatureRange,
            boolean active
    ) {
        this.id = id;
        this.name = name;
        this.storageType = storageType;
        this.temperatureRange = temperatureRange;
        this.active = active;
    }

    /**
     * Factory: Erzeugt eine neue StorageLocation aus rohen Eingaben.
     * Orchestriert Validierung aller VOs intern.
     */
    public static Result<StorageLocationError, StorageLocation> create(StorageLocationDraft draft) {
        // 1. Name validieren (Pflicht)
        StorageLocationName name;
        switch (StorageLocationName.of(draft.name())) {
            case Failure(var err) -> { return Result.failure(err); }
            case Success(var val) -> name = val;
        }

        // 2. StorageType validieren (Pflicht)
        StorageType storageType;
        try {
            storageType = StorageType.valueOf(draft.storageType());
        } catch (IllegalArgumentException | NullPointerException e) {
            return Result.failure(new StorageLocationError.InvalidStorageType(
                    draft.storageType() == null ? "null" : draft.storageType()));
        }

        // 3. TemperatureRange optional
        TemperatureRange temperatureRange = null;
        if (draft.minTemperature() != null || draft.maxTemperature() != null) {
            BigDecimal min;
            BigDecimal max;
            try {
                min = draft.minTemperature() != null ? new BigDecimal(draft.minTemperature()) : null;
                max = draft.maxTemperature() != null ? new BigDecimal(draft.maxTemperature()) : null;
            } catch (NumberFormatException e) {
                return Result.failure(new StorageLocationError.InvalidTemperatureRange(
                        "Temperature values must be valid numbers"));
            }
            switch (TemperatureRange.of(min, max)) {
                case Failure(var err) -> { return Result.failure(err); }
                case Success(var val) -> temperatureRange = val;
            }
        }

        return Result.success(new StorageLocation(
                StorageLocationId.generate(), name, storageType, temperatureRange, true
        ));
    }

    /**
     * Reconstitute from persistence without re-validation.
     */
    public static StorageLocation reconstitute(
            StorageLocationId id,
            StorageLocationName name,
            StorageType storageType,
            TemperatureRange temperatureRange,
            boolean active
    ) {
        return new StorageLocation(id, name, storageType, temperatureRange, active);
    }

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

    /**
     * Wendet partielle Updates an. StorageType ist immutable.
     * null-Felder im Draft werden ignoriert.
     */
    public Result<StorageLocationError, Void> update(StorageLocationUpdateDraft draft) {
        if (draft.name() != null) {
            switch (StorageLocationName.of(draft.name())) {
                case Failure(var err) -> { return Result.failure(err); }
                case Success(var val) -> this.name = val;
            }
        }

        if (draft.minTemperature() != null || draft.maxTemperature() != null) {
            BigDecimal min;
            BigDecimal max;
            try {
                min = draft.minTemperature() != null ? new BigDecimal(draft.minTemperature()) : null;
                max = draft.maxTemperature() != null ? new BigDecimal(draft.maxTemperature()) : null;
            } catch (NumberFormatException e) {
                return Result.failure(new StorageLocationError.InvalidTemperatureRange(
                        "Temperature values must be valid numbers"));
            }
            switch (TemperatureRange.of(min, max)) {
                case Failure(var err) -> { return Result.failure(err); }
                case Success(var val) -> this.temperatureRange = val;
            }
        }

        return Result.success(null);
    }

    public Result<StorageLocationError, Void> deactivate() {
        if (!this.active) {
            return Result.failure(new StorageLocationError.AlreadyInactive());
        }
        this.active = false;
        return Result.success(null);
    }

    public Result<StorageLocationError, Void> activate() {
        if (this.active) {
            return Result.failure(new StorageLocationError.AlreadyActive());
        }
        this.active = true;
        return Result.success(null);
    }

    // ==================== Query Methods ====================

    public boolean isTemperatureControlled() {
        return temperatureRange != null;
    }

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

    public StorageLocationId id() { return id; }
    public StorageLocationName name() { return name; }
    public StorageType storageType() { return storageType; }
    public TemperatureRange temperatureRange() { return temperatureRange; }
    public boolean active() { return active; }

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

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

    @Override
    public String toString() {
        return "StorageLocation{id=" + id + ", name=" + name + ", type=" + storageType + ", active=" + active + "}";
    }
}
