package no.valg.valgnatt.backend.mandatberegning;

import no.valg.valgnatt.database.Konstanter;
import no.valg.valgnatt.database.SisteStemmeskjemaId;
import no.valg.valgnatt.database.domene.ValgType;
import no.valg.valgnatt.database.geografi.Land;
import no.valg.valgnatt.database.geografi.MandatData;
import no.valg.valgnatt.database.geografi.Område;
import no.valg.valgnatt.database.geografi.PartiStemmer;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.jdbc.datasource.DataSourceUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import javax.sql.DataSource;
import java.math.BigDecimal;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;

@Transactional
@Service
public class MandatBeregningLoggService {

	private static final int PROSENT_FAKTOR = 100;
	private static final Logger LOG = LogManager.getLogger(MandatBeregningLoggService.class);

	@Resource
	private DataSource dataSource;

	public void logg(Område område, SisteStemmeskjemaId sisteStemmeskjemaId, boolean prognose) {
		Connection con = null;
		try {
			con = DataSourceUtils.getConnection(dataSource);
			String sql = "INSERT INTO mandatberegning_logg VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?) "
					+ "ON CONFLICT ON CONSTRAINT mandatberegning_logg_pkey DO UPDATE SET "
					+ "oppt_fhs = EXCLUDED.oppt_fhs, oppt_vts = EXCLUDED.oppt_vts, oppt_andel = EXCLUDED.oppt_andel, "
					+ "prog_fhs = EXCLUDED.prog_fhs, prog_vts = EXCLUDED.prog_vts, prog_andel = EXCLUDED.prog_andel, "
					+ "oppt_prosent = EXCLUDED.oppt_prosent, mandat = EXCLUDED.mandat, tape_pp = EXCLUDED.tape_pp";
			try (PreparedStatement ps = con.prepareStatement(sql)) {
				loggOmråde(område, sisteStemmeskjemaId, ps, prognose);
				if (område.getClass() == Land.class) {
					loggFylker((Land) område, sisteStemmeskjemaId, ps, prognose);
				}
				ps.executeBatch();
				loggEventuelleFeil(ps);
			}
		} catch (SQLException sqle) {
			LOG.warn("Feil opstod i mandatberegning-logging, dette er ikke kritisk: " + sqle);
		} finally {
			DataSourceUtils.releaseConnection(con, dataSource);
		}
	}

	private void loggEventuelleFeil(PreparedStatement ps) throws SQLException {
		int[] insertResults = ps.executeBatch();
		for (int i = 0; i < insertResults.length; i++) {
			if (insertResults[i] == PreparedStatement.EXECUTE_FAILED) {
				LOG.error("Feil oppstod i kjøring " + i + " ved logging til mandatberegning_logg tabell. Denne feilen er ikke kritisk for systemet.");
			}
		}
	}

	private void loggFylker(Land område, SisteStemmeskjemaId sisteStemmeskjemaId, PreparedStatement ps, boolean prognose) throws SQLException {
		for (no.valg.valgnatt.database.geografi.Fylke fylke : område.getFylker().values()) {
			loggOmråde(fylke, sisteStemmeskjemaId, ps, prognose);
		}
	}

	@java.lang.SuppressWarnings("squid:S109")
	private void loggOmråde(Område område, SisteStemmeskjemaId sisteStemmeskjemaId, PreparedStatement p, boolean prognose) throws SQLException {
		PartiStemmer totalPs = område.sumStemmerTotal();
		if (totalPs.getStemmerBK() == 0) { // Vi logger ikke områder som ennå ikke har fått stemmer
			return;
		}
		MandatData totalMd = område.sumMandaterTotal().getMandatData(prognose);
		for (PartiStemmer ps : område.aggregerStemmerPrParti(Område.Summering.PARTIER_SOM_STILLER)) {
			if (ps.getStemmerBK() == 0) {
				continue; // Vi logger ikke partier som ennå ikke har fått stemmer
			}
			p.setInt(1, sisteStemmeskjemaId.getId());
			p.setObject(2, område.getValgType().name());
			if (område.getClass() == Land.class) {
				p.setString(3, Konstanter.VALGDISTRIKT_NORGE); // 00
			} else {
				p.setString(3, område.getNr()); // 01-20
			}
			p.setString(4, ps.getPartikode());
			p.setTimestamp(5, sisteStemmeskjemaId.getTms());
			p.setInt(6, ps.getFhs());
			p.setInt(7, ps.getVts());
			p.setBigDecimal(8, new BigDecimal(ps.getStemmer() * PROSENT_FAKTOR).divide(new BigDecimal(totalPs.getStemmerBK()), 1, BigDecimal.ROUND_HALF_UP));
			p.setBigDecimal(9, BigDecimal.valueOf(område.beregnOpptaltProsent() * PROSENT_FAKTOR).divide(BigDecimal.ONE, 1, BigDecimal.ROUND_HALF_UP));
			p.setBigDecimal(10, null); // oppt_avvik - settes i en manuell operasjon etter endt valgkjøring
			MandatData md = område.sumMandaterParti(ps.getPartikode()).getMandatData(prognose);
			p.setInt(11, md.getStemmer());
			p.setInt(12, 0);
			if (totalMd.getStemmerBK() > 0) {
				p.setBigDecimal(13, new BigDecimal(md.getStemmer() * PROSENT_FAKTOR).divide(new BigDecimal(totalMd.getStemmerBK()), 1, BigDecimal.ROUND_HALF_UP));
			} else {
				p.setBigDecimal(13, null);
			}
			p.setBigDecimal(14, null); // prog_avvik - settes i en manuell operasjon etter endt valgkjøring

			if (område.getValgType() == ValgType.FY) { // logger mandatfordeling for ST-valg dersom FY-valg - mest interessant
				p.setBigDecimal(15, new BigDecimal(md.getAntallDistriktsmandaterGittSTValg() + md.getUtjevningmandater().getAntall())); // mandat
			} else {
				p.setBigDecimal(15, new BigDecimal(md.getAntall(område.getValgType()))); // mandat
			}
			p.setBigDecimal(16, null); // mandat_avvik - settes i en manuell operasjon etter endt valgkjøring
			p.setBigDecimal(17, null); // Antall stemmer for å tape mandat - ikke lenger i bruk
			p.setBigDecimal(18, null); // fasit_andel - settes i en manuell operasjon etter endt valgkjøring
			p.setBigDecimal(19, null); // fasit_mandat - settes i en manuell operasjon etter endt valgkjøring
			p.addBatch();
		}
	}
}
