package no.valg.valgnatt.database.hibernate;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import no.valg.valgnatt.database.Konstanter;
import no.valg.valgnatt.database.domene.ForeløpigEndeligOppgjør;
import no.valg.valgnatt.database.domene.OpptaltStatus;
import no.valg.valgnatt.database.domene.Valg;
import no.valg.valgnatt.database.domene.ValgType;
import no.valg.valgnatt.database.domene.ValgtingForhand;
import no.valg.valgnatt.database.geografi.Kommune;
import no.valg.valgnatt.database.geografi.Område;
import no.valg.valgnatt.database.geografi.PartiStemmer;

import javax.persistence.Column;
import javax.persistence.EmbeddedId;
import javax.persistence.Entity;
import javax.persistence.Table;
import java.sql.Timestamp;
import java.util.Collection;
import java.util.Map;
import java.util.TreeMap;

@Entity
@Table(name = "stemmekretser")
@NoArgsConstructor // Hibernate-krav
@AllArgsConstructor //Builder-krav
@Builder
public class Stemmekrets extends Område implements java.io.Serializable {

    @Getter
    @Setter
    @EmbeddedId
    private StemmekretsId id;

    @Getter
    @Setter
    @Column(name = "bydelnr")
    private String bydelnr;

    @Getter
    @Setter
    @Column(name = "kretsnavn", length = 140)
    private String kretsnavn;

    @Getter
    @Setter
    @Column(name = "stemmeberettigede", nullable = false)
    private Integer stemmeberettigede;

    @Getter
    @Setter
    @Column(name = "opptaltstatusmax", nullable = false)
    private Integer opptaltStatusMax;

    @Getter
    @Setter
    @Builder.Default
    private transient int refId = -1; // default til en umulig refId
    @Getter
    private transient int nestsisteStemmeberettigede; // default 0
    @Getter
    @Setter
    private transient Timestamp endretTms; // tidspunkt for nyeste stemmetall i kretsen
    @Setter
    private transient int opptaltStatus; // faktisk opptaltstatus
    @Getter
    @Setter
    private transient no.valg.valgnatt.database.geografi.Kommune kommune;
    private final transient Map<String, PartiStemmer> partiMap = new TreeMap<>();

    // Felter som settes i oppgjøret - vil være null inntil da
    @Getter
    @Setter
    private transient Integer stgFhsGodkjente;
    @Getter
    @Setter
    private transient Integer stgGodkjente;
    @Getter
    @Setter
    private transient Integer stgFhsForkastede;
    @Getter
    @Setter
    private transient Integer stgForkastede;
    @Getter
    @Setter
    private transient Integer stsFhsForkastede;
    @Getter
    @Setter
    private transient Integer stsForkastede;

    // For bedre ytelse - husker stemmekrets-map'et
    private final transient Map<String, Stemmekrets> stemmekretser = new TreeMap<>();

    @Override
    public String getNr() {
        return id.getKretsnr();
    }

    @Override
    public String getNavn() {
        return getKretsnavn();
    }

    @Override
    public String getRapportid() {
        return kommune.getRapportid() + "/" + getNr();
    }

    @Override
    public Map<String, Stemmekrets> getStemmekretser() {
        if (stemmekretser.size() == 0) {
            stemmekretser.put(getId().getKretsnr(), this);
        }
        return stemmekretser;
    }

    @Override
    public Map<String, Kommune> getKommuner() {
        return null;
    }

    @Override
    public Valgdistrikt getValgdistrikt() {
        return null;
    }

    /**
     * Kan ikke leverer frammøteprosent for stemmesedler, fordi FHS ikke er kretsfordelt (eller bydelfordelt)
     */
    @Override
    public Float beregnFrammoteProsentIaar() {
        return null;
    }

    @Override
    public Område getOverordnetOmråde() {
        if (getValgType() != ValgType.SA && kommune.getNr().equals(Konstanter.OSLO_KNR)) {
            return kommune.getBydel(bydelnr);
        } else {
            return kommune;
        }
    }

    @Override
    public Område[] getUnderordnedeRapportOmråder() {
        return new Område[]{};
    }

    public OpptaltStatus getOSMax() {
        return OpptaltStatus.values()[opptaltStatusMax];
    }

    public void setPartistemmer(PartiStemmer partiStemmer) {
        partiMap.put(partiStemmer.getPartikode(), partiStemmer);
    }

    public PartiStemmer getParti(String partikode) {
        return partiMap.get(partikode);
    }

    public Collection<PartiStemmer> getPartier() {
        return partiMap.values();
    }

    public int getOpptaltStatus() {
        if (getOSMax() == OpptaltStatus.INGEN) {
            return OpptaltStatus.OPPGJØR.getStatus();
        }
        return opptaltStatus;
    }

    public OpptaltStatus getOS() {
        return OpptaltStatus.values()[getOpptaltStatus()];
    }

    // Gir en prosentandel av kommunen, basert på antall stemmeberettigede
    public float andelAvKommune() {
        return (float) stemmeberettigede / kommune.getAntallStemmeberettigede();
    }

    public void addNestsisteStemmeberettigede(int nestsisteStemmeberettigede) {
        this.nestsisteStemmeberettigede += nestsisteStemmeberettigede;
    }

    public boolean erDefaultKrets() {
        return getNr().equals(Konstanter.DEFAULT_KRETS);
    }

    public boolean erOpptalt(ForeløpigEndeligOppgjør feo, ValgtingForhand vf) {
        if (vf == null && getOSMax() == OpptaltStatus.ALLE_ENDELIG) {
            return erOpptaltForhånd(feo) && erOpptaltValgting(feo);
        } else if (getOSMax() == OpptaltStatus.INGEN) {
            return true;
        } else if (vf == ValgtingForhand.F || getOSMax() == OpptaltStatus.BARE_FHS_ENDELIG) {
            return erOpptaltForhånd(feo);
        } else if (vf == ValgtingForhand.V || getOSMax() == OpptaltStatus.BARE_VTS_ENDELIG) {
            return erOpptaltValgting(feo);
        }
        return false; // Skal aldri inntreffe
    }

    private boolean erOpptaltForhånd(ForeløpigEndeligOppgjør feo) {
        if (getOS() == OpptaltStatus.OPPGJØR) {
            // Dersom i VTS-krets, så kan det ikke komme noe FHS her, så da kan vi ikke returnere "true"
            return getOSMax() != OpptaltStatus.BARE_VTS_ENDELIG;
        }
        if (feo == ForeløpigEndeligOppgjør.OPPGJØR) {
            return false;
        }
        if (getOS() == OpptaltStatus.ALLE_ENDELIG) {
            // Dersom i VTS-krets, så kan det ikke komme noe FHS her, så da kan vi ikke returnere "true"
            return getOSMax() != OpptaltStatus.BARE_VTS_ENDELIG;
        }
        // Behandler øverige statuser (1-7) her, trenger bare å håndtere FORELØPIG og ENDELIG
        if (feo == ForeløpigEndeligOppgjør.ENDELIG) {
            return getOS() == OpptaltStatus.BARE_FHS_ENDELIG || getOS() == OpptaltStatus.FHS_END_VTS_FPG;
        } else { // feo == ForeløpigEndeligOppgjør.FORELØPIG
            return getOS() == OpptaltStatus.BARE_FHS_FORELØPIG || getOS() == OpptaltStatus.BARE_FHS_ENDELIG || opptaltStatus >= OpptaltStatus.ALLE_FORELØPIG.getStatus();
        }
    }

    private boolean erOpptaltValgting(ForeløpigEndeligOppgjør feo) {
        if (getOS() == OpptaltStatus.OPPGJØR) {
            // Dersom i FHS-krets, så kan det ikke komme noe VTS her, så da kan vi ikke returnere "true"
            return getOSMax() != OpptaltStatus.BARE_FHS_ENDELIG;
        }
        if (feo == ForeløpigEndeligOppgjør.OPPGJØR) {
            return false;
        }
        if (getOS() == OpptaltStatus.ALLE_ENDELIG) {
            // Dersom i FHS-krets, så kan det ikke komme noe VTS her, så da kan vi ikke returnere "true"
            return getOSMax() != OpptaltStatus.BARE_FHS_ENDELIG;
        }

        if (feo == ForeløpigEndeligOppgjør.ENDELIG) {
            return getOS() == OpptaltStatus.BARE_VTS_ENDELIG || getOS() == OpptaltStatus.FHS_FPG_VTS_END || opptaltStatus >= OpptaltStatus.ALLE_ENDELIG.getStatus();
        } else { // ForeløpigEndeligOppgjør.FOPELØPIG
            // Hvis default-krets i kretsfordelt kommune, så sier vi at VTS foreløpig er opptalt dersom FHS foreløpig er opptalt (vi får aldri VTS foreløpig da)
            if (getKommune().harKretsvisOpptelling() && erDefaultKrets()) {
                return opptaltStatus >= OpptaltStatus.BARE_FHS_FORELØPIG.getStatus();
            } else {
                return getOS() == OpptaltStatus.BARE_VTS_FORELØPIG || getOS() == OpptaltStatus.BARE_VTS_ENDELIG || opptaltStatus >= OpptaltStatus.ALLE_FORELØPIG.getStatus();
            }
        }
    }

    public int getStemmer(Valg valg, ForeløpigEndeligOppgjør feo, ValgtingForhand vf) {
        if (valg == Valg.DETTE) {
            int antallStemmer = 0;
            for (PartiStemmer ps : getPartier()) {
                if (feo == ForeløpigEndeligOppgjør.FORELØPIG) {
                    antallStemmer += getStemmer(vf, ps);
                } else if (feo == ForeløpigEndeligOppgjør.ENDELIG) {
                    antallStemmer += getStemmerEndelig(vf, ps);
                } else if (feo == ForeløpigEndeligOppgjør.OPPGJØR) {
                    antallStemmer += getOS() == OpptaltStatus.OPPGJØR ? getStemmer(vf, ps) : 0;
                }
            }
            return antallStemmer;
        } else {
            return getPartier().stream().mapToInt(ps -> ps.getStemmer(valg, vf)).sum(); // historiske valg har bare "endelige stemmer"
        }
    }

    // Alle stemmer teller som foreløpige (selv om de er endelige) - fordi foreløpige tellinger
    // blir uansett overskrevet av endelige tellinger
    private int getStemmer(ValgtingForhand vf, PartiStemmer ps) {
        if (vf == null) {
            return ps.getStemmer();
        } else if (vf == ValgtingForhand.F) {
            return ps.getFhs();
        } else { // ValgtingForhand.V - listestemmer blir aldri etterspurt
            return ps.getVts();
        }
    }

    private int getStemmerEndelig(ValgtingForhand vf, PartiStemmer ps) {
        boolean endeligFhs = harEnAvDisseStatusene(OpptaltStatus.BARE_FHS_ENDELIG, OpptaltStatus.FHS_END_VTS_FPG);
        boolean endeligVts = harEnAvDisseStatusene(OpptaltStatus.BARE_VTS_ENDELIG, OpptaltStatus.FHS_FPG_VTS_END);
        boolean endeligAlle = harEnAvDisseStatusene(OpptaltStatus.ALLE_ENDELIG, OpptaltStatus.OPPGJØR);
        int antallStemmer = 0;
        if ((vf == null || vf == ValgtingForhand.F) && (endeligFhs || endeligAlle)) {
            antallStemmer += ps.getFhs();
        }
        if ((vf == null || vf == ValgtingForhand.V) && (endeligVts || endeligAlle)) {
            antallStemmer += ps.getVts();
        }
        return antallStemmer;
    }

    private boolean harEnAvDisseStatusene(OpptaltStatus os1, OpptaltStatus os2) {
        return opptaltStatus == os1.getStatus() || opptaltStatus == os2.getStatus();
    }

    @Override
    public boolean isSisteSammenlignbar() {
        return false;
    }

    @Override
    public boolean isNestSisteSammenlignbar() {
        return false;
    }
}
