/*
 * Copyright (c) 2013 L2jMobius
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
 * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
 * IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */
package org.l2jmobius.gameserver.managers;

import java.io.IOException;
import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Executors;
import java.util.logging.Level;
import java.util.logging.Logger;

import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpServer;

import org.l2jmobius.gameserver.config.custom.FakePlayersConfig;

/**
 * Tiny embedded HTTP dashboard: live bot map and a dialog tester with seam breakdown.
 * Endpoints: / (html), /api/bots, /api/seams, /api/test?msg=...&personality=...
 * @author Mobius SPP
 */
public class FakePlayerDashboard
{
	private static final Logger LOGGER = Logger.getLogger(FakePlayerDashboard.class.getName());
	private static final int PORT = 7780;
	
	protected FakePlayerDashboard()
	{
		if (!FakePlayersConfig.FAKE_PLAYERS_ENABLED)
		{
			return;
		}
		
		try
		{
			final HttpServer server = HttpServer.create(new InetSocketAddress("0.0.0.0", PORT), 0);
			server.setExecutor(Executors.newFixedThreadPool(2));
			server.createContext("/", exchange -> respondFile(exchange));
			server.createContext("/api/bots", exchange ->
			{
				final String npcs = FakePlayerProgressionManager.getInstance().statusJson();
				final String headless = FakePlayerHeadlessManager.getInstance().statusJson();
				final String merged = headless.isEmpty() ? npcs : (npcs.length() > 2 ? npcs.substring(0, npcs.length() - 1) + "," + headless + "]" : "[" + headless + "]");
				respond(exchange, "application/json", merged);
			});
			server.createContext("/api/seams", exchange -> respond(exchange, "application/json", seamsJson()));
			server.createContext("/api/stats", exchange -> respond(exchange, "application/json", FakePlayerTelemetry.getInstance().statsJson()));
			server.createContext("/api/sieges", exchange -> respond(exchange, "text/plain", FakePlayerSiegeManager.getInstance().info()));
			server.createContext("/api/phrase", exchange -> respond(exchange, "application/json", phraseJson(exchange)));
			server.createContext("/api/cmd", exchange -> respond(exchange, "application/json", cmdJson(exchange)));
			server.createContext("/api/test", exchange -> respond(exchange, "application/json", testJson(exchange)));
			server.start();
			LOGGER.info(getClass().getSimpleName() + ": Listening on port " + PORT + " (http://127.0.0.1:" + PORT + "/).");
		}
		catch (Exception e)
		{
			LOGGER.log(Level.WARNING, getClass().getSimpleName() + ": Could not start.", e);
		}
	}
	
	private void respondFile(HttpExchange exchange) throws IOException
	{
		byte[] body;
		try
		{
			body = Files.readAllBytes(Paths.get("./data/dashboard.html"));
		}
		catch (Exception e)
		{
			body = "dashboard.html not found in game/data".getBytes(StandardCharsets.UTF_8);
		}
		exchange.getResponseHeaders().set("Content-Type", "text/html; charset=utf-8");
		exchange.sendResponseHeaders(200, body.length);
		try (OutputStream os = exchange.getResponseBody())
		{
			os.write(body);
		}
	}
	
	private void respond(HttpExchange exchange, String type, String content) throws IOException
	{
		final byte[] body = content.getBytes(StandardCharsets.UTF_8);
		exchange.getResponseHeaders().set("Content-Type", type + "; charset=utf-8");
		exchange.sendResponseHeaders(200, body.length);
		try (OutputStream os = exchange.getResponseBody())
		{
			os.write(body);
		}
	}
	
	private static String esc(String value)
	{
		final StringBuilder sb = new StringBuilder();
		for (char c : value.toCharArray())
		{
			switch (c)
			{
				case '"':
					sb.append("\\\"");
					break;
				case '\\':
					sb.append("\\\\");
					break;
				case '\n':
				case '\r':
					sb.append(' ');
					break;
				default:
					sb.append(c);
			}
		}
		return sb.toString();
	}
	
	private String seamsJson()
	{
		final StringBuilder sb = new StringBuilder(1 << 16);
		sb.append('{');
		boolean firstSeam = true;
		for (Map.Entry<String, List<String>> entry : FakePlayerChatLines.getInstance().seams().entrySet())
		{
			if (!firstSeam)
			{
				sb.append(',');
			}
			firstSeam = false;
			sb.append('"').append(esc(entry.getKey())).append("\":[");
			boolean firstLine = true;
			for (String line : entry.getValue())
			{
				if (!firstLine)
				{
					sb.append(',');
				}
				firstLine = false;
				sb.append('"').append(esc(line)).append('"');
			}
			sb.append(']');
		}
		return sb.append('}').toString();
	}
	
	private Map<String, String> query(HttpExchange exchange)
	{
		final Map<String, String> result = new HashMap<>();
		final String query = exchange.getRequestURI().getRawQuery();
		if (query != null)
		{
			for (String pair : query.split("&"))
			{
				final int eq = pair.indexOf('=');
				if (eq > 0)
				{
					result.put(pair.substring(0, eq), URLDecoder.decode(pair.substring(eq + 1), StandardCharsets.UTF_8));
				}
			}
		}
		return result;
	}
	
	private String phraseJson(HttpExchange exchange)
	{
		final Map<String, String> params = query(exchange);
		final String key = params.getOrDefault("key", "ambient");
		final String personality = params.getOrDefault("personality", "neutral");
		final Map<String, String> slots = new HashMap<>();
		slots.put("player", params.getOrDefault("player", "Vanger"));
		slots.put("target", params.getOrDefault("player", "Vanger"));
		slots.put("level", "42");
		slots.put("boss", "Core");
		slots.put("zone", "Cruma");
		slots.put("town", "Giran");
		slots.put("msg", "продам дроп");
		final StringBuilder sb = new StringBuilder("[");
		for (int i = 0; i < 8; i++)
		{
			final String line = FakePlayerChatLines.getInstance().compose(0, key, personality, slots);
			if (i > 0)
			{
				sb.append(',');
			}
			sb.append('"').append(esc((line != null) ? line : "")).append('"');
		}
		return sb.append(']').toString();
	}
	
	private String cmdJson(HttpExchange exchange)
	{
		final Map<String, String> params = query(exchange);
		final String command = params.getOrDefault("do", "");
		final String name = params.getOrDefault("name", "");
		final String argument = params.getOrDefault("arg", "");
		String result;
		try
		{
			result = FakePlayerHeadlessManager.getInstance().debugCommand(command, name, argument);
		}
		catch (Exception e)
		{
			result = "error: " + e;
		}
		return "{\"result\":\"" + esc(result) + "\"}";
	}
	
	private String testJson(HttpExchange exchange)
	{
		String message = "";
		String personality = "neutral";
		final String query = exchange.getRequestURI().getRawQuery();
		if (query != null)
		{
			for (String pair : query.split("&"))
			{
				final int eq = pair.indexOf('=');
				if (eq > 0)
				{
					final String key = pair.substring(0, eq);
					final String value = URLDecoder.decode(pair.substring(eq + 1), StandardCharsets.UTF_8);
					if (key.equals("msg"))
					{
						message = value;
					}
					else if (key.equals("personality"))
					{
						personality = value;
					}
				}
			}
		}
		
		final String normalized = FakePlayerIntentParser.getInstance().normalize(message);
		final FakePlayerIntentParser.Intent intent = FakePlayerIntentParser.getInstance().parse(message);
		String seam = "";
		String hint = "";
		String line = "";
		if (intent != null)
		{
			hint = FakePlayerChatManager.intentHint(intent.key);
			seam = intent.seam;
			// Mirror the effect-dependent seam choices of the live pipeline (dry run).
			switch (intent.key)
			{
				case "insult":
					seam = ("ganker".equals(personality) || "pkk".equals(personality)) ? "reply_insult_aggro" : "reply_insult_soft";
					break;
				case "ask_help":
					seam = "ganker".equals(personality) ? "reply_help_no" : "reply_help_yes";
					break;
				case "ask_party":
					seam = "ganker".equals(personality) ? "reply_party_no" : "party_accept";
					break;
			}
			if (!seam.isEmpty())
			{
				final Map<String, String> slots = new HashMap<>();
				slots.put("player", "Tester");
				slots.put("zone", "Cruma");
				slots.put("level", "42");
				final String picked = FakePlayerChatLines.getInstance().compose(0, seam, personality, slots);
				line = (picked != null) ? picked : "";
			}
		}
		
		return "{\"normalized\":\"" + esc(normalized) + "\",\"intent\":\"" + esc(intent != null ? intent.key : "") + "\",\"hint\":\"" + esc(hint) + "\",\"seam\":\"" + esc(seam) + "\",\"line\":\"" + esc(line) + "\"}";
	}
	
	public static FakePlayerDashboard getInstance()
	{
		return SingletonHolder.INSTANCE;
	}
	
	private static class SingletonHolder
	{
		protected static final FakePlayerDashboard INSTANCE = new FakePlayerDashboard();
	}
}
