package controllers;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import net.nationstatesplusplus.assembly.model.HappeningType;
import net.nationstatesplusplus.assembly.model.Nation;
import net.nationstatesplusplus.assembly.util.DatabaseAccess;
import net.nationstatesplusplus.assembly.util.Utils;

import org.apache.commons.dbutils.DbUtils;
import org.joda.time.Duration;
import org.spout.cereal.config.yaml.YamlConfiguration;

import play.libs.Json;
import play.mvc.Result;

public class WorldAssemblyController extends DatabaseController {

	public WorldAssemblyController(DatabaseAccess access, YamlConfiguration config) {
		super(access, config);
	}

	public Result getWorldAssemblyResolutionVotingStats() throws SQLException {
		List<Map<String, Object>> resolutions = new ArrayList<Map<String, Object>>();
		try (Connection conn = getConnection()) {
			try (PreparedStatement select = conn.prepareStatement("SELECT wa.id, wa.category, wa.desc, wa.name, wa.proposer, wa.created, wa.council, delegate_votes_against, delegate_votes_for, nation_votes_against, nation_votes_for FROM assembly.wa_resolutions AS wa LEFT JOIN assembly.final_wa_votes AS v ON wa.id = v.wa_resolution")) {
				try (ResultSet result = select.executeQuery()) {
					while (result.next()) {
						Map<String, Object> json = new HashMap<String, Object>();
						json.put("id", result.getInt("id"));
						json.put("category", result.getString("category"));
						json.put("description", result.getString("desc"));
						json.put("name", result.getString("name"));
						json.put("proposer", result.getString("proposer"));
						json.put("created", result.getLong("created"));
						json.put("council", result.getInt("council"));
						json.put("delegate_votes_against", result.getInt("delegate_votes_against"));
						json.put("delegate_votes_for", result.getInt("delegate_votes_for"));
						json.put("nation_votes_against", result.getInt("nation_votes_against"));
						json.put("nation_votes_for", result.getInt("nation_votes_for"));
						
						resolutions.add(json);
					}
				}
			}
		}
		
		Result result = Utils.handleDefaultGetHeaders(request(), response(), String.valueOf(resolutions.hashCode()), "3600");
		if (result != null) {
			return result;
		}
		return ok(Json.toJson(resolutions)).as("application/json");
	}

	public Result getWAMembers(String region) throws SQLException {
		Map<String, Map<String, Object>> json = new HashMap<String, Map<String, Object>>();
		Connection conn = null; 
		try {
			conn = getConnection();
			PreparedStatement statement = conn.prepareStatement("SELECT title, influence, influence_desc, count(e.endorsed) AS endorsements from assembly.nation AS n LEFT OUTER JOIN assembly.endorsements AS e ON n.id = e.endorsed WHERE alive = 1 AND wa_member = 1 AND region = ? GROUP BY title;");
			statement.setInt(1, getDatabase().getRegionId(region));
			ResultSet result = statement.executeQuery();
			while(result.next()) {
				String nation = result.getString(1);
				HashMap<String, Object> values = new HashMap<String, Object>();
				values.put("endorsements", result.getInt(4));
				values.put("influence", result.getInt(2));
				values.put("influence_desc", result.getString(3));
				json.put(nation, values);
			}
			DbUtils.closeQuietly(result);
			DbUtils.closeQuietly(statement);
		} finally {
			DbUtils.closeQuietly(conn);
		}

		Result result = Utils.handleDefaultGetHeaders(request(), response(), String.valueOf(json.hashCode()));
		if (result != null) {
			return result;
		}
		return ok(Json.toJson(json)).as("application/json");
	}

	public Result getEndorsements(String name, boolean fullData) throws SQLException {
		List<Object> nations = new ArrayList<Object>();
		Connection conn = null; 
		try {
			conn = getConnection();
			PreparedStatement statement = conn.prepareStatement("SELECT n.title " + (fullData ? ", n.name, n.id, n.full_name, n.flag " : "") + "FROM assembly.nation AS n LEFT OUTER JOIN assembly.endorsements AS e ON n.id = e.endorsed WHERE e.endorser = ?");
			statement.setInt(1, getDatabase().getNationId(name));
			ResultSet result = statement.executeQuery();
			while(result.next()) {
				String title = result.getString(1);
				nations.add(fullData ? (new Nation(result.getString(2), title, result.getString(4), result.getString(5), result.getInt(3), true, true, -1)) : title);
			}
			DbUtils.closeQuietly(result);
			DbUtils.closeQuietly(statement);
		} finally {
			DbUtils.closeQuietly(conn);
		}

		Result result = Utils.handleDefaultGetHeaders(request(), response(), String.valueOf(nations.hashCode()));
		if (result != null) {
			return result;
		}
		return ok(Json.toJson(nations)).as("application/json");
	}

	public Result getMissingEndorsements(String name, boolean fullData) throws SQLException {
		List<Object> nations = new ArrayList<Object>();
		Connection conn = null; 
		try {
			int nationId = getDatabase().getNationId(name);
			HashSet<Integer> endorsements = new HashSet<Integer>();
			conn = getConnection();
			PreparedStatement statement = conn.prepareStatement("SELECT endorsed FROM assembly.endorsements WHERE endorser = ?");
			statement.setInt(1, nationId);
			ResultSet result = statement.executeQuery();
			while(result.next()) {
				endorsements.add(result.getInt(1));
			}
			DbUtils.closeQuietly(result);
			DbUtils.closeQuietly(statement);
			
			statement = conn.prepareStatement("SELECT id, title " + (fullData ? ", name, full_name, flag " : "") + "FROM assembly.nation WHERE alive = 1 AND wa_member = 1 AND region = (SELECT region FROM assembly.nation WHERE id = ?)");
			statement.setInt(1, nationId);
			result = statement.executeQuery();
			while(result.next()) {
				if (!endorsements.contains(result.getInt(1)) && result.getInt(1) != nationId) {
					nations.add(fullData ? (new Nation(result.getString(3), result.getString(2), result.getString(4), result.getString(5), result.getInt(1), true, true, -1)) : result.getString(2));
				}
			}
			DbUtils.closeQuietly(result);
			DbUtils.closeQuietly(statement);
		} finally {
			DbUtils.closeQuietly(conn);
		}

		Result result = Utils.handleDefaultGetHeaders(request(), response(), String.valueOf(nations.hashCode()));
		if (result != null) {
			return result;
		}
		return ok(Json.toJson(nations)).as("application/json");
	}

	public Result getUnreturnedEndorsements(String name, boolean fullData) throws SQLException {
		List<Object> nations = new ArrayList<Object>();
		Connection conn = null; 
		try {
			int nationId = getDatabase().getNationId(name);
			HashSet<Integer> endorsements = new HashSet<Integer>();
			conn = getConnection();
			PreparedStatement statement = conn.prepareStatement("SELECT endorser FROM assembly.endorsements WHERE endorsed = ?");
			statement.setInt(1, nationId);
			ResultSet result = statement.executeQuery();
			while(result.next()) {
				endorsements.add(result.getInt(1));
			}
			DbUtils.closeQuietly(result);
			DbUtils.closeQuietly(statement);
			
			statement = conn.prepareStatement("SELECT id, title " + (fullData ? ", name, full_name, flag " : "") + "FROM assembly.nation WHERE alive = 1 AND wa_member = 1 AND region = (SELECT region FROM assembly.nation WHERE id = ?)");
			statement.setInt(1, nationId);
			result = statement.executeQuery();
			while(result.next()) {
				if (!endorsements.contains(result.getInt(1)) && result.getInt(1) != nationId) {
					nations.add(fullData ? (new Nation(result.getString(3), result.getString(2), result.getString(4), result.getString(5), result.getInt(1), true, true, -1)) : result.getString(2));
				}
			}
			DbUtils.closeQuietly(result);
			DbUtils.closeQuietly(statement);
		} finally {
			DbUtils.closeQuietly(conn);
		}

		Result result = Utils.handleDefaultGetHeaders(request(), response(), String.valueOf(nations.hashCode()));
		if (result != null) {
			return result;
		}
		return ok(Json.toJson(nations)).as("application/json");
	}

	public Result getWADelegates() throws SQLException {
		List<String> delegates = new ArrayList<String>();
		Connection conn = null; 
		try {
			conn = getConnection();
			PreparedStatement select = conn.prepareStatement("SELECT delegate FROM assembly.region WHERE delegate <> \"0\" AND alive = 1");
			ResultSet set = select.executeQuery();
			while(set.next()) {
				delegates.add(set.getString(1));
			}
			DbUtils.closeQuietly(set);
			DbUtils.closeQuietly(select);
		} finally {
			DbUtils.closeQuietly(conn);
		}
		Result result = Utils.handleDefaultGetHeaders(request(), response(), String.valueOf(delegates.hashCode()));
		if (result != null) {
			return result;
		}
		return ok(Json.toJson(delegates)).as("application/json");
	}

	private Map<String, Object> getResolution(Connection conn, int council) throws SQLException {
		Map<String, Object> resolution = new HashMap<String, Object>();
		PreparedStatement select = conn.prepareStatement("SELECT id, name FROM assembly.wa_resolutions WHERE council = ? ORDER BY created DESC LIMIT 0, 1");
		select.setInt(1, council);
		ResultSet set = select.executeQuery();
		if (set.next()) {
			resolution.put("id", set.getInt(1));
			resolution.put("name", set.getString(2));
			PreparedStatement votes = conn.prepareStatement("SELECT timestamp, nation_votes_against, nation_votes_for FROM assembly.wa_votes WHERE wa_resolution = ? ORDER BY timestamp DESC LIMIT 0, 1");
			votes.setInt(1, set.getInt(1));
			ResultSet voteResults = votes.executeQuery();
			if (voteResults.next()) {
				resolution.put("last_update", voteResults.getLong(1));
				resolution.put("nation_votes_against", voteResults.getInt(2));
				resolution.put("nation_votes_for", voteResults.getInt(3));
			}
			DbUtils.closeQuietly(voteResults);
			DbUtils.closeQuietly(votes);
		}
		DbUtils.closeQuietly(set);
		DbUtils.closeQuietly(select);
		return resolution;
	}

	public Result getIndividualWAVotes() throws SQLException {
		List<Map<String, Object>> resolutions = new LinkedList<Map<String, Object>>();
		Connection conn = null;
		try {
			conn = getConnection();
			long lastUpdate = Integer.MAX_VALUE;
			//We are assuming the newest resolutions are the ones at vote...is this always true?
			resolutions.add(getResolution(conn, 0));
			resolutions.add(getResolution(conn, 1));
			
			for (Map<String, Object> res : resolutions) {
				lastUpdate = Math.min(lastUpdate, (Long)res.get("last_update"));
			}
			
			int expires = 300;
			if (lastUpdate != Integer.MAX_VALUE) {
				lastUpdate /= 1000L;
				//Votes update each hour. Tell client to keep response until next hourly update.
				expires = (int) (lastUpdate + 3600 - (System.currentTimeMillis() / 1000L));
			} else {
				expires = 300;
			}
			Result result = Utils.handleDefaultGetHeaders(request(), response(), String.valueOf(resolutions.hashCode()), String.valueOf(expires));
			if (result != null) {
				return result;
			}
			return ok(Json.toJson(resolutions)).as("application/json");
		} finally {
			DbUtils.closeQuietly(conn);
		}
	}

	private List<Map<String, String>> powerTransfers = null;
	private long nextCache = 0;
	public Result getRecentPowerTransfers() throws SQLException {
		if (powerTransfers == null || nextCache < System.currentTimeMillis()) {
			Connection conn = null;
			PreparedStatement select = null;
			ResultSet result = null;
			List<Map<String, String>> transfers = new ArrayList<Map<String, String>>();
			try {
				conn = getConnection();
				select = conn.prepareStatement("SELECT g.type, re.name, re.title, re.flag, n.name AS nation_name, n.title AS nation_title, n.flag AS nation_flag, g.timestamp FROM ((assembly.global_happenings AS g LEFT JOIN assembly.regional_happenings AS r ON g.id = r.global_id) LEFT JOIN assembly.region AS re ON re.id = r.region) LEFT JOIN assembly.nation AS n ON n.id = g.nation WHERE g.timestamp > ? AND (type = 32 OR g.type = 33) ORDER BY g.timestamp DESC");
				select.setLong(1, System.currentTimeMillis() - Duration.standardHours(30).getMillis());
				result = select.executeQuery();
				while(result.next()) {
					Map<String, String> data = new HashMap<String, String>();
					data.put("type", HappeningType.getType(result.getInt(1)).getName());
					data.put("region", result.getString(2));
					data.put("region_title", result.getString(3));
					data.put("region_flag", result.getString(4));
					data.put("delegate", result.getString(5));
					data.put("delegate_title", result.getString(6));
					data.put("delegate_flag", result.getString(7));
					data.put("timestamp", String.valueOf(result.getLong(8)));
					
					transfers.add(data);
				}
			} finally {
				DbUtils.closeQuietly(conn);
			}
			
			powerTransfers = transfers;
			nextCache = System.currentTimeMillis() + Duration.standardHours(1).getMillis();
		}
		
		Result r = Utils.handleDefaultGetHeaders(request(), response(), String.valueOf(powerTransfers.hashCode()), "21600");
		if (r != null) {
			return r;
		}
		return ok(Json.toJson(powerTransfers)).as("application/json");
	}
}