package no.valg.valgnatt.backend.parser;

import java.sql.Timestamp;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;

import javax.annotation.Resource;
import no.valg.valgnatt.database.Configuration;
import no.valg.valgnatt.database.EvaAdminSkjema;
import no.valg.valgnatt.database.GrunnlagsdataCache;
import no.valg.valgnatt.database.Konstanter;
import no.valg.valgnatt.database.domene.StemmeType;
import no.valg.valgnatt.database.domene.ValgType;
import no.valg.valgnatt.database.domene.ValideringStatus;
import no.valg.valgnatt.database.hibernate.Stemmekrets;
import no.valg.valgnatt.database.hibernate.Stemmeoppgjor;
import no.valg.valgnatt.database.hibernate.Stemmeskjema;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.stereotype.Component;

import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;

@Component
public class MainParser {

    private static final int FEMTEN_HUNDRE_TEGN = 1500;
    private static final Logger LOG = LogManager.getLogger(MainParser.class);
    @Resource
    private ParserService parserService;
    @Resource
    private GrunnlagsdataCache grunnlagsdataCache;
    @Resource
    private Configuration configuration;

    public void process(List<Stemmeskjema> rader) {
        for (Stemmeskjema rad : rader) {
            prosesserStemmeskjema(rad);
        }
    }

    private void prosesserStemmeskjema(Stemmeskjema stemmeskjema) {
        long startTime = System.currentTimeMillis();

        Skjemadata skjemadata;
        try {
            skjemadata = parseJsonStemmeskjema(stemmeskjema.getData());
        } catch (Exception e) {
            feilSkjema(stemmeskjema, "Feil i parsing av stemmeskjema", e);
            return;
        }

        oppdaterStemmeskjemaMedData(stemmeskjema, skjemadata);

        Stemmekrets krets = validerKommuneOgKrets(stemmeskjema);
        if (krets == null) {
            return;
        }
        oppdaterStemmekretsOsloFyMedData(stemmeskjema, skjemadata);
        oppdaterStemmekretsMedData(stemmeskjema, skjemadata, krets);
        oppdaterStemmeskjemaMedOpptaltstatus(stemmeskjema, skjemadata, krets);

		
		setStatus(stemmeskjema, ValideringStatus.M, "");

        long lagreStart = System.currentTimeMillis();
        try {
            parserService.lagreSkjema(skjemadata.getStemmeresultater(), skjemadata.getStemmeoppgjør(), stemmeskjema);
        } catch (Exception e) {
            feilSkjema(stemmeskjema, "Feil ved lagring", e);
            return;
        }
        LOG.info("Prosessert stemmeskjema " + stemmeskjema.getId() + " for "
                + stemmeskjema.getValgType() + " " + stemmeskjema.getKommnr() + "-" + stemmeskjema.getKretsnr()
                + " på " + (System.currentTimeMillis() - startTime) + " ms (" + (System.currentTimeMillis() - lagreStart) + " ms)");
    }

    private Stemmekrets validerKommuneOgKrets(Stemmeskjema stemmeskjema) {
        if (grunnlagsdataCache.getKommune(stemmeskjema.getValgType(), stemmeskjema.getKommnr()) == null) {
            feilSkjema(stemmeskjema, "Kommune finnes ikke " + stemmeskjema.getKommnr(), null);
            return null;
        }
        Stemmekrets krets = grunnlagsdataCache.getStemmekrets(stemmeskjema.getValgType(), stemmeskjema.getKommnr(), stemmeskjema.getKretsnr());
        if (krets == null) {
            feilSkjema(stemmeskjema, "Krets " + stemmeskjema.getKommnr() + "-" + stemmeskjema.getKretsnr() + " finnes ikke", null);
            return null;
        }
        return krets;
    }

    private void oppdaterStemmeskjemaMedData(Stemmeskjema stemmeskjema, Skjemadata skjemadata) {
        stemmeskjema.setValgType(skjemadata.getValgtype());
        stemmeskjema.setSkjemaType(skjemadata.getSkjemaType());
        stemmeskjema.setKommnr(skjemadata.getKommnr());
        stemmeskjema.setKretsnr(skjemadata.getKretsnr());
        stemmeskjema.setOriginalTid(skjemadata.getOriginalTid());
    }

	private void oppdaterStemmekretsOsloFyMedData(Stemmeskjema stemmeskjema, Skjemadata skjemadata) {
        if (stemmeskjema.erKommunestyrevalgForOslo() && grunnlagsdataCache.hasValgType(ValgType.FY)) {
            Stemmekrets oslokrets = grunnlagsdataCache.getStemmekrets(ValgType.FY, stemmeskjema.getKommnr(), stemmeskjema.getKretsnr());
            oppdaterStemmekretsMedData(stemmeskjema, skjemadata, oslokrets);
        }
    }

    private void oppdaterStemmekretsMedData(Stemmeskjema stemmeskjema, Skjemadata skjemadata, Stemmekrets krets) {
        krets.setRefId(stemmeskjema.getId());
        krets.setOpptaltStatus(skjemadata.getOpptaltStatus(krets.getOpptaltStatusMax()).getStatus());
        krets.setEndretTms(new Timestamp(skjemadata.getOriginalTid().getTime()));
    }

    private void oppdaterStemmeskjemaMedOpptaltstatus(Stemmeskjema stemmeskjema, Skjemadata skjemadata, Stemmekrets krets) {
        stemmeskjema.setOpptaltstatus(skjemadata.getOpptaltStatus(krets.getOpptaltStatusMax()).getStatus());
    }private Skjemadata parseJsonStemmeskjema(String data) throws ParseException {
		JsonParser parser = new JsonParser();
		JsonObject jsonObject = parser.parse(data).getAsJsonObject();
		String kommnr = jsonObject.get(EvaAdminSkjema.KOMMUNE).getAsString().split(" ")[0];
		String kretsnr = jsonObject.get(EvaAdminSkjema.STEMMEKRETS).getAsString().split(" ")[0];
		ValgType valgtype = ValgType.valueOf(jsonObject.get(EvaAdminSkjema.VALGTYPE).getAsString().split(" ")[0]);
		String skjematype = jsonObject.get(EvaAdminSkjema.SKJEMATYPE).getAsString().split(" ")[0];

        Date originalTid = (new SimpleDateFormat("yyyyMMdd-HHmmss")).parse(jsonObject.get(EvaAdminSkjema.TIDSPUNKT).getAsString());
        Skjemadata js = new Skjemadata(valgtype, skjematype, kommnr, kretsnr, originalTid);
        Stemmeresultater sr = new Stemmeresultater();

        JsonArray stemmerArray = jsonObject.get("stemmer").getAsJsonArray();
        for (int i = 0; i < stemmerArray.size(); i++) {
            oppdaterStemmerPrParti(kretsnr, sr, stemmerArray, i, valgtype);
        }
        js.setStemmeresultater(sr);
        JsonObject statistikk = jsonObject.getAsJsonObject("statistikk");
        if (statistikk != null) {
            oppdaterStatistikk(js, sr, statistikk);
        }
        return js;
    }

    private void oppdaterStemmerPrParti(String kretsnr, Stemmeresultater sr, JsonArray stemmerArray, int i, ValgType valgType) {
        JsonObject stemmer = stemmerArray.get(i).getAsJsonObject();
        String partikode = stemmer.get("partikode").getAsString();
        if (configuration.ignorerUkjentePartier() && grunnlagsdataCache.getPartiliste(valgType, partikode) == null) {
            return;
        }
        if (stemmer.has("fhs-foreløpig")) {
            sr.oppdaterPartiStemmer(StemmeType.FHS_FPG, partikode, stemmer.get("fhs-foreløpig").getAsInt());
        }
        if (stemmer.has("vts-foreløpig")) {
            sr.oppdaterPartiStemmer(StemmeType.VTS_FPG, partikode, stemmer.get("vts-foreløpig").getAsInt());
        }
        if (stemmer.has("fhs-endelig")) {
            sr.oppdaterPartiStemmer(StemmeType.FHS_END, partikode, stemmer.get("fhs-endelig").getAsInt());
        }
        if (stemmer.has("vts-endelig")) {
            sr.oppdaterPartiStemmer(StemmeType.VTS_END, partikode, stemmer.get("vts-endelig").getAsInt());
        }
        if (stemmer.has("lis") && (grunnlagsdataCache.getValgAar() < Konstanter.LISTESTEMMER_SUMMERT_I_DEFAULT_KRETS_ÅR || kretsnr.equals(Konstanter.DEFAULT_KRETS)
        )) {
            sr.oppdaterPartiStemmer(StemmeType.END_LIST, partikode, stemmer.get("lis").getAsInt());
        }
    }

    private void oppdaterStatistikk(Skjemadata js, Stemmeresultater sr, JsonObject statistikk) {
        Stemmeoppgjor so = new Stemmeoppgjor();
        so.setStgForhandForkastede(statistikk.get("stg-forhånd-forkastede").getAsInt());
        so.setStgForhandGodkjente(statistikk.get("stg-forhånd-godkjente").getAsInt());
        so.setStgForkastede(statistikk.get("stg-valgting-forkastede").getAsInt() + statistikk.get("stg-forhånd-forkastede").getAsInt());
        so.setStgGodkjente(statistikk.get("stg-valgting-godkjente").getAsInt() + statistikk.get("stg-forhånd-godkjente").getAsInt());
        so.setStsForhandForkastede(statistikk.get("sts-forhånd-forkastede").getAsInt());
        so.setStsForhandGodkjente(statistikk.get("sts-forhånd-godkjente").getAsInt());
        so.setStsForkastede(statistikk.get("sts-valgting-forkastede").getAsInt() + statistikk.get("sts-forhånd-forkastede").getAsInt());
        so.setStsGodkjente(statistikk.get("sts-valgting-godkjente").getAsInt() + statistikk.get("sts-forhånd-godkjente").getAsInt());
        if (sr.getPartikoder().contains(Konstanter.BLANKE)) {
            so.setStsBlanke(sr.getFhs(Konstanter.BLANKE) + sr.getVts(Konstanter.BLANKE));
            so.setStsForhandBlanke(sr.getFhs(Konstanter.BLANKE));
        } else {
            so.setStsBlanke(0);
            so.setStsForhandBlanke(0);
        }
        js.setStemmeoppgjør(so);
    }

    private void feilSkjema(Stemmeskjema stemmeskjema, String msg, Exception ex) {
        if (ex == null) {
            LOG.warn("Feil i behandling for stemmeskjema " + stemmeskjema.getId() + ": " + msg);
            setStatus(stemmeskjema, ValideringStatus.F, msg);
        } else {
            LOG.warn("Feil i behandling for stemmeskjema: " + stemmeskjema.getId() + " " + msg, ex);
            setStatus(stemmeskjema, ValideringStatus.F, msg + " " + ex.getMessage());
        }
        lagreStatus(stemmeskjema);
    }

    private void setStatus(Stemmeskjema stemmeskjema, ValideringStatus status, String msg) {
        stemmeskjema.setValideringstatus(status);
        stemmeskjema.setValideringInfo(msg.length() <= FEMTEN_HUNDRE_TEGN ? msg : msg.substring(0, FEMTEN_HUNDRE_TEGN));
    }

    private void lagreStatus(Stemmeskjema stemmeskjema) {
        parserService.oppdaterStemmeskjema(stemmeskjema);
    }

}
