package de.accso.flexinale.domain.model;

import de.accso.flexinale.shared_kernel.*;
import org.apache.commons.lang3.builder.EqualsBuilder;
import org.apache.commons.lang3.builder.HashCodeBuilder;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;

import static de.accso.flexinale.shared_kernel.RawWrapper.getRawOrNull;

public class Kontingent implements Identifiable, Versionable, EqualsByContent {
    public record RestKontingent(Integer raw) implements RawWrapper<Integer> {}

    public enum KontingentType {
        ONLINE,
        ZENTRAL,
        KINOKASSE
    }

    public final Id id;
    public final Version version;

    private final RestKontingent restKontingentOnline;
    private final RestKontingent restKontingentZentral;
    private final RestKontingent restKontingentKinokasse;

    public Kontingent(final Id id,
                      final RestKontingent restKontingentOnline,
                      final RestKontingent restKontingentZentral,
                      final RestKontingent restKontingentKinokasse) {
        this(id, Versionable.unknownVersion(),
             restKontingentOnline, restKontingentZentral, restKontingentKinokasse);
    }

    public Kontingent(final Id id, final Version version,
                      final RestKontingent restKontingentOnline,
                      final RestKontingent restKontingentZentral,
                      final RestKontingent restKontingentKinokasse) {
        this.id = id;
        this.version = version;
        this.restKontingentOnline = restKontingentOnline;
        this.restKontingentZentral = restKontingentZentral;
        this.restKontingentKinokasse = restKontingentKinokasse;
    }

    public Kontingent(final Id id,
                      final int gesamtKapazitaetVerkauf, final int quoteOnline) {
        this(id, Versionable.unknownVersion(), gesamtKapazitaetVerkauf, quoteOnline);
    }

    public Kontingent(final Id id, final Version version,
                      final int gesamtKapazitaetVerkauf, final int quoteOnline) {
        this.id = id;
        this.version = version;

        restKontingentOnline    = new RestKontingent((int) Math.ceil ((float)(gesamtKapazitaetVerkauf * quoteOnline)/100));
        restKontingentZentral   = new RestKontingent((int) Math.floor((float)(gesamtKapazitaetVerkauf - restKontingentOnline.raw)/2));
        restKontingentKinokasse = new RestKontingent(gesamtKapazitaetVerkauf - restKontingentZentral.raw - restKontingentOnline.raw);
    }

    @Override
    public Id id() {
        return id;
    }

    @Override
    public Version version() {
        return version;
    }

    public RestKontingent getRestKontingentOnline() {
        return restKontingentOnline;
    }

    public RestKontingent getRestKontingentZentral() {
        return restKontingentZentral;
    }

    public RestKontingent getRestKontingentKinokasse() {
        return restKontingentKinokasse;
    }

    public Kontingent reduziereKontingent(final KontingentType kontingentType, final int kontingentReduktion) {
        if (kontingentReduktion < 0) {
            throw new FlexinaleIllegalArgumentException("reduction of Kontingent has to be >=0 but is "
                    + kontingentReduktion);
        }

        return switch (kontingentType) {
            case ONLINE ->
                    new Kontingent(this.id, this.version,
                            reduziereDiesesKontingent(getRawOrNull(restKontingentOnline), kontingentReduktion),
                            this.restKontingentZentral,
                            this.restKontingentKinokasse
                    );
            case ZENTRAL ->
                    new Kontingent(this.id, this.version,
                            this.restKontingentOnline,
                            reduziereDiesesKontingent(getRawOrNull(restKontingentZentral), kontingentReduktion),
                            this.restKontingentKinokasse
                    );
            case KINOKASSE ->
                    new Kontingent(this.id, this.version,
                            this.restKontingentOnline,
                            this.restKontingentZentral,
                            reduziereDiesesKontingent(getRawOrNull(restKontingentKinokasse), kontingentReduktion)
                    );
        };
    }

    private RestKontingent reduziereDiesesKontingent(final int kontingent, final int reduziereUm) {
        int reduziertesKontingent = kontingent - reduziereUm;
        if (reduziertesKontingent < 0) {
            throw new KontingentBereitsAusgeschoepftException();
        }

        return new RestKontingent(reduziertesKontingent);
    }

    @Override
    public String toString() {
        return ToStringBuilder.reflectionToString(this, ToStringStyle.MULTI_LINE_STYLE);
    }

    @Override
    public boolean equals(final Object o) {
        if (!equalsByContent(o)) return false;

        if (this == o) {return true;}
        if (o == null || getClass() != o.getClass()) {return false;}
        Kontingent that = (Kontingent) o;
        return new EqualsBuilder().append(version, that.version).isEquals();
    }

    @Override
    public boolean equalsByContent(final Object o) {
        if (this == o) {return true;}

        if (o == null || getClass() != o.getClass()) {return false;}

        Kontingent that = (Kontingent) o;

        return new EqualsBuilder()
                .append(id, that.id)
                .append(restKontingentOnline, that.restKontingentOnline)
                .append(restKontingentZentral, that.restKontingentZentral)
                .append(restKontingentKinokasse, that.restKontingentKinokasse)
                .isEquals();
    }

    @Override
    public int hashCode() {
        return new HashCodeBuilder(17, 37)
                .append(id).append(version)
                .append(restKontingentOnline)
                .append(restKontingentZentral).append(restKontingentKinokasse)
                .toHashCode();
    }
}
