package no.valg.valgnatt.mottak;

import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.concurrent.TimeUnit;

import javax.annotation.PostConstruct;
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.ValgType;
import no.valg.valgnatt.database.domene.ValideringStatus;
import no.valg.valgnatt.database.geografi.Kommune;
import no.valg.valgnatt.database.hibernate.Stemmekrets;
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;

@Component
public class MainMottak implements Runnable {


	private static final Logger LOG = LogManager.getLogger(MainMottak.class);
	private static final int POLL_INTERVAL = 5000; // Milliseconds
	private static final int CONNECTION_TIMEOUT_MS = 10000;
	private static final int READ_TIMEOUT_MS = 60000;

	private volatile boolean isShutdown;
	private volatile int maxId;

	@Resource
	private MottakService mottakService;
	@Resource
	private Configuration configuration;
	@Resource
	private GrunnlagsdataCache grunnlagsdataCache;

	@PostConstruct
	public void initialize() {
		maxId = mottakService.getMaxRefId();
	}

	@Override
	public void run() {
		int nextId = maxId + 1;
		isShutdown = configuration.getMottakFromURL() == null;
		while (!isShutdown) {
			long startTimeNs = System.nanoTime();
			String data = httpGet(configuration.getMottakFromURL() + "?id=" + nextId);
			if (data != null) {
				lagreStemmeskjema(data, configuration.getMottakFromURL());
				nextId++;
			} else {
				vent(startTimeNs);
			}
		}
	}

	private void vent(long startTimeNs) {
		long tidBruktMs = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startTimeNs);
		if (tidBruktMs < POLL_INTERVAL) {
			try {
				Thread.sleep(POLL_INTERVAL - tidBruktMs);
			} catch (InterruptedException ie) {
				// Nothing
			}
		}
	}

	private String httpGet(String url) {
		HttpURLConnection conn = null;
		int responseCode;
		String responseMessage;
		String result;
		try {
			conn = (HttpURLConnection) new URL(url).openConnection();
			conn.setConnectTimeout(CONNECTION_TIMEOUT_MS);
			conn.setReadTimeout(READ_TIMEOUT_MS);
			conn.connect();
			responseCode = conn.getResponseCode();
			responseMessage = conn.getResponseMessage();
			InputStream is = conn.getErrorStream();
			if (is == null) {
				is = conn.getInputStream();
			}
			BufferedReader br = new BufferedReader(new InputStreamReader(is));
			String line;
			StringBuilder sb = new StringBuilder();
			while ((line = br.readLine()) != null) {
				sb.append(line).append('\n');
			}
			br.close();
			result = sb.toString();
		} catch (Exception e) {
			LOG.warn("Connection failure, " + e.getMessage() + " " + url, e);
			return null;
		} finally {
			if (conn != null) {
				conn.disconnect();
			}
		}
		if (responseCode == HttpURLConnection.HTTP_OK) {
			return result;
		}
		if (responseCode != HttpURLConnection.HTTP_NOT_FOUND || !result.contains("no data found")) {
			LOG.warn(responseCode + " " + responseMessage + " " + url);
		}
		return null;
	}

	void lagreStemmeskjema(String data, String from) {
		long start = System.currentTimeMillis();
		Stemmeskjema stemmeskjema = new Stemmeskjema();
		stemmeskjema.setData(data);
		stemmeskjema.setValideringstatus(ValideringStatus.I);
		stemmeskjema.setValgType(ValgType.ST);   // Hibernate bug-workaround, tillater ikke null for enum, overskrives uansett med riktig verdi senere
		stemmeskjema.setOpptaltstatus(0);
		mottakService.lagreStemmeskjema(stemmeskjema);

		maxId = stemmeskjema.getId();

		// Plukker ut litt tekst som er fin å logge hvis den er der
		LOG.info("Mottatt: ID (" + (System.currentTimeMillis() - start) + " ms) " + maxId + " fra " + from + " med id " + data.substring(2, data.indexOf("stemmer") -
				2));
	}

	void shutdown() {
		isShutdown = true;
	}

	String getData(int refId) {
		String data = null;
		if (refId <= maxId) {
			Stemmeskjema stemmeskjema = mottakService.getById(refId);
			if (stemmeskjema != null) {
				data = stemmeskjema.getData();
			}
		}
		return data;
	}

	void oppdaterStemmeberettigede(EvaAdminSkjema evaAdminSkjema) throws MottakException {
		JsonObject jsonObject = evaAdminSkjema.getJsonObject();
		JsonArray fylker = jsonObject.get("fylker").getAsJsonArray();
		for (int i = 0; i < fylker.size(); i++) {
			JsonObject fylke = fylker.get(i).getAsJsonObject();
			JsonArray kommuner = fylke.get("kommuner").getAsJsonArray();
			for (int j = 0; j < kommuner.size(); j++) {
				JsonObject kommune = kommuner.get(j).getAsJsonObject();
				String kommnr = kommune.get("kommunenummer").getAsString();
				JsonArray kretser = kommune.get("kretser").getAsJsonArray();
				for (int k = 0; k < kretser.size(); k++) {
					JsonObject krets = kretser.get(k).getAsJsonObject();
					String kretsnr = krets.get("kretsnummer").getAsString();
					Integer antallSB = krets.get("stemmeberettigede").getAsInt();
					oppdaterEvaAdminSkjema(evaAdminSkjema, kommnr, kretsnr, antallSB);
				}
			}
		}
	}

	private void oppdaterEvaAdminSkjema(EvaAdminSkjema evaAdminSkjema, String kommnr, String kretsnr, Integer antallSB) throws MottakException {
		Kommune kommune = grunnlagsdataCache.getKommune(evaAdminSkjema.getValgtype(), kommnr);
		if (kommune != null) {
			Stemmekrets stemmekrets = kommune.getStemmekrets(kretsnr);
			if (stemmekrets != null) {
				oppdaterStemmekretsMedNyeSb(evaAdminSkjema, kommnr, kretsnr, antallSB, stemmekrets);
				// Oppdaterer også Oslos stemmeberettigede i det korresponderende FY-valget
				if (evaAdminSkjema.getValgtype() == ValgType.KO && kommnr.equals(Konstanter.OSLO_KNR)) {
					stemmekrets = grunnlagsdataCache.getStemmekrets(ValgType.FY, kommnr, kretsnr);
					oppdaterStemmekretsMedNyeSb(evaAdminSkjema, kommnr, kretsnr, antallSB, stemmekrets);
				}
			} else {
				throw new MottakException("Oppdatering av stemmeberettigede feilet: Stemmekrets " + kretsnr + " i kommune " + kommnr + " (" + kommune.getNavn() + ") i" +
						" valgtype " + kommune.getValgType() + " " + "finnes ikke");
			}
		} else {
			throw new MottakException("Oppdatering av stemmeberettigede feilet: Kommune " + kommnr + " i valgtype " + evaAdminSkjema.getValgtype() + " finnes ikke");
		}
	}

	private void oppdaterStemmekretsMedNyeSb(EvaAdminSkjema evaAdminSkjema, String kommnr, String kretsnr, Integer antallSB, Stemmekrets stemmekrets) {
		if (stemmekrets.getStemmeberettigede().intValue() != antallSB) {
			LOG.info(evaAdminSkjema.getValgtype() + "-" + kommnr + "-" + kretsnr + ": Endrer antall stemmeberettigede i databasen fra  " + stemmekrets
					.getStemmeberettigede() + " til " + antallSB);
			stemmekrets.setStemmeberettigede(antallSB);
			mottakService.oppdaterStemmekrets(stemmekrets);
		}
	}

	boolean finnesGrunnlag(EvaAdminSkjema evaAdminSkjema) {
		return grunnlagsdataCache.getLand(evaAdminSkjema.getValgtype()) != null && grunnlagsdataCache.getValgAar() == evaAdminSkjema.getValgår();
	}
}
