import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;

import org.l2jmobius.gameserver.managers.FakePlayerBotContext;
import org.l2jmobius.gameserver.managers.FakePlayerChatLines;
import org.l2jmobius.gameserver.managers.FakePlayerHeardManager;
import org.l2jmobius.gameserver.managers.FakePlayerIdiolect;
import org.l2jmobius.gameserver.managers.FakePlayerIntentParser;

/**
 * Offline harness for the bot chat engine: runs the real server classes
 * (FakePlayerChatLines, FakePlayerIntentParser, FakePlayerIdiolect,
 * FakePlayerBotContext, FakePlayerHeardManager) against the real XML data
 * without a game server. Run from a directory that contains data/ (see
 * tools/run_harness.sh). Exit code 1 on any failed check.
 */
public class ChatHarness
{
	private static int failures = 0;
	private static int checks = 0;

	private static void check(boolean condition, String what)
	{
		checks++;
		if (!condition)
		{
			failures++;
			System.out.println("  FAIL: " + what);
		}
	}

	public static void main(String[] args)
	{
		final FakePlayerChatLines lines = FakePlayerChatLines.getInstance();
		final FakePlayerIntentParser intents = FakePlayerIntentParser.getInstance();

		// 1. Every seam the code uses must exist and produce a line with full slots.
		final String[] codeSeams =
		{
			"greeting", "whisper_default", "reply_where", "reply_price", "reply_help_yes", "reply_help_no", "reply_party_no", "party_accept", "reply_insult_soft", "reply_insult_aggro", "reply_thanks", "reply_botcheck", "reply_duel", "reply_rumor_none", "reply_bye", "reply_yes_ok", "reply_how", "reply_caps", "reply_again", "overheard_q", "reply_level", "gank_start", "pkk_start", "war_start", "killed_by", "revenge_meet", "friendly_meet", "ambient", "levelup", "grats", "migrate", "raid_start", "trade_shout", "invite_player", "call_help", "lfp", "overheard", "assist", "companion_done", "store_title", "bot_talk", "reply_drop", "siege_start", "siege_defend", "siege_gate", "siege_engrave", "siege_win", "siege_lose", "profession", "died_mob", "loot_brag", "reply_clan", "reply_solo", "reply_long", "reply_pk"
		};
		final Map<String, String> slots = new HashMap<>();
		slots.put("player", "Vanger");
		slots.put("target", "Vanger");
		slots.put("level", "42");
		slots.put("boss", "Core");
		slots.put("zone", "Cruma");
		slots.put("town", "Giran");
		slots.put("msg", "продам дроп");
		slots.put("prev", "не пойду");
		slots.put("enemyclan", "RedCrew");
		slots.put("mob", "Cruma Marshlands Traitor");
		slots.put("castle", "Giran");
		slots.put("clan", "IronPact");
		slots.put("class", "Gladiator");
		slots.put("drop", "Oriharukon Ore");
		slots.put("item", "соски D");
		slots.put("price", "50к");
		slots.put("crowd", "6");
		System.out.println("== seams used by code (" + codeSeams.length + ") ==");
		final Map<String, String> samples = new TreeMap<>();
		for (String seam : codeSeams)
		{
			String ok = null;
			for (int botId = 1; (botId <= 12) && (ok == null); botId++)
			{
				final Map<String, String> facts = new HashMap<>();
				facts.put("personality", (botId % 4 == 0) ? "ganker" : (botId % 4 == 1) ? "helper" : (botId % 4 == 2) ? "pkk" : "neutral");
				facts.put("asker", (botId % 3 == 0) ? "stranger" : (botId % 3 == 1) ? "clanmate" : "killed_me");
				ok = lines.speak(botId, seam, facts, slots, false, null);
			}
			check(ok != null, "seam produces nothing: " + seam);
			if (ok != null)
			{
				samples.put(seam, ok);
			}
		}
		for (Map.Entry<String, String> entry : samples.entrySet())
		{
			System.out.println("  " + entry.getKey() + ": " + entry.getValue());
		}

		// 2. Context facts change what a bot says (plan / event / grounding slots).
		System.out.println("== context ==");
		FakePlayerBotContext.setPlan(77, "siege", 60000);
		FakePlayerBotContext.setSlot(77, "castle", "Aden");
		int siegeMentions = 0;
		for (int i = 0; i < 40; i++)
		{
			final String line = lines.speak(77, "ambient", Map.of("personality", "neutral"), null, false, null);
			if ((line != null) && (line.contains("осад") || line.contains("Aden")))
			{
				siegeMentions++;
			}
		}
		System.out.println("  plan=siege -> siege mentions in 40 ambient lines: " + siegeMentions);
		check(siegeMentions >= 8, "plan=siege rule should fire regularly (got " + siegeMentions + ")");
		FakePlayerBotContext.setPlan(78, "farm", 0);
		FakePlayerBotContext.setEvent(78, "profession");
		int professionMentions = 0;
		for (int i = 0; i < 40; i++)
		{
			final String line = lines.speak(78, "ambient", Map.of("personality", "neutral"), null, false, null);
			if ((line != null) && line.contains("проф"))
			{
				professionMentions++;
			}
		}
		System.out.println("  event=profession -> mentions in 40 ambient lines: " + professionMentions);
		check(professionMentions >= 2, "event=profession rule should fire (got " + professionMentions + ")");
		FakePlayerBotContext.setSlot(79, "drop", "Adamantite Nugget");
		FakePlayerBotContext.setSlot(79, "mob", "Dion Grizzly");
		final String dropLine = lines.speak(79, "reply_drop", Map.of("personality", "neutral"), Map.of("player", "Vanger"), false, null);
		System.out.println("  reply_drop with grounding slots: " + dropLine);
		check(dropLine != null, "reply_drop with slots from context");
		final String noDrop = lines.speak(80, "reply_drop", Map.of("personality", "neutral"), Map.of("player", "Vanger"), false, null);
		System.out.println("  reply_drop without drop slot: " + noDrop);
		check(noDrop != null, "reply_drop must have slot-free lines");

		// 3. Bot to bot openers are understood by the listener (intent) or fall back to overheard.
		System.out.println("== bot_talk -> intents ==");
		final Map<String, Integer> distribution = new LinkedHashMap<>();
		int understood = 0;
		final int openers = 200;
		for (int i = 0; i < openers; i++)
		{
			final int speaker = 100 + (i % 20);
			final String opener = lines.speak(speaker, "bot_talk", Map.of("personality", "neutral"), Map.of("player", "Kolyan", "level", "35"), false, null);
			if (opener == null)
			{
				continue;
			}
			final FakePlayerIntentParser.Intent intent = intents.parse(opener);
			final String key = (intent != null) ? intent.key : (FakePlayerHeardManager.isQuestionLine(opener) ? "(overheard_q)" : "(overheard)");
			distribution.merge(key, 1, Integer::sum);
			if (intent != null)
			{
				understood++;
			}
			if (i < 12)
			{
				System.out.println("  \"" + opener + "\" -> " + key);
			}
		}
		System.out.println("  intents: " + distribution);
		check(understood >= (openers / 2), "at least half of bot_talk openers should map to an intent (got " + understood + "/" + openers + ")");

		// 4. Sample dialogue as the social layer would run it (opener -> reply seam of the intent).
		System.out.println("== sample dialogues ==");
		for (int i = 0; i < 6; i++)
		{
			final int a = 300 + i;
			final int b = 400 + i;
			final String opener = lines.speak(a, "bot_talk", Map.of("personality", "neutral"), Map.of("player", "Zheka"), false, null);
			final FakePlayerIntentParser.Intent intent = (opener != null) ? intents.parse(opener) : null;
			String reply;
			if ((intent != null) && !intent.seam.isEmpty())
			{
				final Map<String, String> replySlots = new HashMap<>(slots);
				replySlots.put("player", "Vovan");
				reply = lines.speak(b, intent.seam, Map.of("personality", "helper", "asker", "clanmate"), replySlots, false, FakePlayerIdiolect.Mirror.of(opener));
			}
			else
			{
				reply = lines.speak(b, (opener != null) && FakePlayerHeardManager.isQuestionLine(opener) ? "overheard_q" : "overheard", Map.of("personality", "helper"), Map.of("msg", FakePlayerHeardManager.echoOf(opener != null ? opener : ""), "player", "Vovan"), true, null);
			}
			System.out.println("  Vovan: " + opener);
			System.out.println("  Zheka: " + reply + "   [" + ((intent != null) ? intent.key : "overheard") + "]");
		}

		// 5. Idiolect: two bots never sound the same.
		System.out.println("== idiolect ==");
		final FakePlayerIdiolect one = FakePlayerIdiolect.of(1001);
		final FakePlayerIdiolect two = FakePlayerIdiolect.of(1002);
		check(!(one.laugh.equals(two.laugh) && one.filler.equals(two.filler) && (one.chattiness == two.chattiness) && (one.slang == two.slang)), "idiolects should differ");
		System.out.println("  1001: laugh=" + one.laugh + " filler=" + one.filler + " slang=" + one.slang + " mat=" + one.traitMat);
		System.out.println("  1002: laugh=" + two.laugh + " filler=" + two.filler + " slang=" + two.slang + " mat=" + two.traitMat);

		// 6. Heard classification helpers.
		check(FakePlayerHeardManager.isQuestionLine("кто знает где рб"), "cyrillic question detection");
		check(!FakePlayerHeardManager.isQuestionLine("фарм идет норм"), "statement is not a question");
		check(FakePlayerHeardManager.isCommercialLine("продам соски дешево"), "commercial detection");
		check("продам соски дешево".startsWith(FakePlayerHeardManager.echoOf("продам соски дешево налетай")), "echo takes first words");

		// 7. Intents that sieges / economy rely on.
		final FakePlayerIntentParser.Intent drop = intents.parse("дроп есть?");
		check((drop != null) && "ask_drop".equals(drop.key), "ask_drop intent");
		final FakePlayerIntentParser.Intent level = intents.parse("какой лвл");
		check((level != null) && "ask_level".equals(level.key), "ask_level intent");
		final FakePlayerIntentParser.Intent party = intents.parse("го в пати");
		check((party != null) && "ask_party".equals(party.key), "ask_party intent");
		final FakePlayerIntentParser.Intent how = intents.parse("как фарм?");
		check((how != null) && "how_are_you".equals(how.key), "how_are_you intent");
		final String[][] regression =
		{
			{ "го пвп", "duel" }, { "помгите", "ask_help" }, { "дуель?", "duel" }, { "ты бот?", "botcheck" }, { "спс бро", "thanks" },
			{ "лох", "insult" }, { "где ты", "ask_where" }, { "что нового", "ask_rumors" }, { "пока", "bye" }, { "прив", "greeting" },
			{ "ок", "yes_ok" }, { "сколько стоит", "ask_price" }, { "клан есть?", "ask_clan" }, { "ты соло?", "ask_solo" },
			{ "давно тут стоишь?", "ask_long" }, { "пк ходят?", "ask_pk" }, { "здарова, как жизнь молодая?", "how_are_you" },
			{ "где качаться думаешь", "ask_where" }
		};
		for (String[] pair : regression)
		{
			final FakePlayerIntentParser.Intent parsed = intents.parse(pair[0]);
			final String got = (parsed != null) ? parsed.key : "null";
			check(got.equals(pair[1]), "intent of \"" + pair[0] + "\": expected " + pair[1] + ", got " + got);
		}
		final String[] mustBeNull = { "думаешь тут фармить?", "молодая", "стоишь тут" };
		for (String text : mustBeNull)
		{
			final FakePlayerIntentParser.Intent parsed = intents.parse(text);
			check((parsed == null) || "ask_long".equals(parsed.key) || "ask_where".equals(parsed.key), "no false positive for \"" + text + "\" (got " + ((parsed != null) ? parsed.key : "null") + ")");
		}

		System.out.println("== " + (checks - failures) + "/" + checks + " checks passed ==");
		System.exit(failures == 0 ? 0 : 1);
	}
}
