package wtf.sten.vault.ui;

import com.google.gson.JsonObject;
import net.minecraft.client.gui.GuiGraphicsExtractor;
import net.minecraft.client.gui.components.EditBox;
import net.minecraft.client.gui.screens.Screen;
import net.minecraft.network.chat.Component;
import net.minecraft.util.Util;
import wtf.sten.vault.Faces;
import wtf.sten.vault.Proxy;
import wtf.sten.vault.SessionSwap;
import wtf.sten.vault.StenNet;
import wtf.sten.vault.StenVault;
import wtf.sten.vault.VaultConfig;

import java.net.URI;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Locale;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;

public class VaultScreen extends Screen {
	private static final String[] RANKS = {"", "VIP", "VIP+", "MVP", "MVP+", "MVP++", "NONE"};
	private final Screen parent;
	private EditBox search;
	private EditBox capeBox;
	private EditBox minValueBox;
	private EditBox minDonutBox;
	private EditBox proxyBox;
	private int scroll;
	private String lastQuery = "";
	private int lastCount = -1;
	private String lastPairing = "";
	private String lastSort = "";
	private String lastRank = "";
	private String lastCape = "";
	private String lastMinValue = "";
	private String lastMinDonut = "";
	private final List<GlassButton> rows = new ArrayList<>();
	private final List<JsonObject> visible = new ArrayList<>();

	public VaultScreen(Screen parent) {
		super(Component.literal("STEN Vault"));
		this.parent = parent;
	}

	@Override
	protected void init() {
		super.init();
		clearWidgets();
		rows.clear();
		int cx = this.width / 2;
		search = field(24, 52, this.width - 48, "Filter name, rank, uid, cape");
		capeBox = field(24, 76, 110, "Cape");
		minValueBox = field(140, 76, 90, "Min $");
		minDonutBox = field(236, 76, 90, "Min Donut");
		proxyBox = field(24, this.height - 52, 220, "socks5://user:pass@host:port");
		capeBox.setValue(VaultConfig.get().capeFilter);
		if (VaultConfig.get().minValue > 0) minValueBox.setValue(trimNum(VaultConfig.get().minValue));
		if (VaultConfig.get().minDonut > 0) minDonutBox.setValue(trimNum(VaultConfig.get().minDonut));
		proxyBox.setValue(VaultConfig.get().proxy);

		if (StenNet.session.signedIn()) {
			addRenderableWidget(new GlassButton(this.width - 330, 12, 74, 22, Component.literal("Refresh"), false, b -> {
				StenNet.status = "Loading sten.wtf…";
				CompletableFuture.runAsync(StenNet::refreshAccounts);
			}));
			addRenderableWidget(new GlassButton(this.width - 248, 12, 74, 22, Component.literal(sortLabel()), false, b -> {
				cycleSort();
				relayout();
			}));
			addRenderableWidget(new GlassButton(this.width - 166, 12, 74, 22, Component.literal(rankLabel()), false, b -> {
				cycleRank();
				relayout();
			}));
			addRenderableWidget(new GlassButton(this.width - 84, 12, 72, 22, Component.literal("Sign out"), false, b -> {
				StenNet.signOut();
				relayout();
			}));
		} else {
			addRenderableWidget(new GlassButton(cx - 110, 12, 220, 24, Component.literal("Sign in with sten.wtf"), true, b -> {
				StenNet.status = "Opening sten.wtf…";
				StenNet.beginLogin(msg -> StenNet.status = msg);
			}));
		}

		addRenderableWidget(new GlassButton(252, this.height - 52, 70, 20, Component.literal("Proxy"), true, b -> {
			String next = proxyBox.getValue().trim();
			if (!next.isEmpty() && Proxy.parse(next) == null) {
				StenNet.status = "Invalid SOCKS5 proxy";
				return;
			}
			VaultConfig.get().proxy = next;
			VaultConfig.get().save();
			StenNet.status = next.isEmpty() ? "Direct connection" : "Proxy set";
		}));
		addRenderableWidget(new GlassButton(24, this.height - 28, 82, 20, Component.literal("Dashboard"), true, b -> Util.getPlatform().openUri(URI.create(StenVault.DASHBOARD))));
		addRenderableWidget(new GlassButton(110, this.height - 28, 70, 20, Component.literal("Source"), false, b -> Util.getPlatform().openUri(URI.create(StenVault.SOURCE))));
		addRenderableWidget(new GlassButton(184, this.height - 28, 62, 20, Component.literal("Bugs"), false, b -> Util.getPlatform().openUri(URI.create(StenVault.ISSUES))));
		addRenderableWidget(new GlassButton(250, this.height - 28, 82, 20, Component.literal("Download"), false, b -> Util.getPlatform().openUri(URI.create(StenVault.DOWNLOAD))));
		rebuildRows();
	}

	private EditBox field(int x, int y, int w, String hint) {
		EditBox box = new EditBox(this.font, x, y, w, 18, Component.literal(hint));
		box.setHint(Component.literal(hint));
		box.setBordered(false);
		box.setTextColor(Glass.INK);
		addRenderableWidget(box);
		return box;
	}

	private void relayout() {
		this.init();
	}

	private void cycleSort() {
		VaultConfig cfg = VaultConfig.get();
		cfg.sort = switch (cfg.sort) {
			case "newest" -> "value";
			case "value" -> "donut";
			case "donut" -> "networth";
			case "networth" -> "level";
			case "level" -> "capes";
			case "capes" -> "name";
			default -> "newest";
		};
		cfg.save();
	}

	private void cycleRank() {
		VaultConfig cfg = VaultConfig.get();
		String current = cfg.rankFilter == null ? "" : cfg.rankFilter;
		int idx = 0;
		for (int i = 0; i < RANKS.length; i++) {
			if (RANKS[i].equalsIgnoreCase(current)) {
				idx = (i + 1) % RANKS.length;
				break;
			}
		}
		cfg.rankFilter = RANKS[idx];
		cfg.save();
	}

	private String sortLabel() {
		return switch (VaultConfig.get().sort) {
			case "value" -> "Value";
			case "donut" -> "Donut";
			case "networth" -> "NW";
			case "level" -> "Level";
			case "capes" -> "Capes";
			case "name" -> "Name";
			default -> "New";
		};
	}

	private String rankLabel() {
		String rank = VaultConfig.get().rankFilter;
		return rank == null || rank.isBlank() ? "Rank" : rank;
	}

	private void persistFilters() {
		VaultConfig cfg = VaultConfig.get();
		cfg.capeFilter = capeBox == null ? "" : capeBox.getValue().trim();
		cfg.minValue = parseNum(minValueBox);
		cfg.minDonut = parseNum(minDonutBox);
		cfg.save();
	}

	private void rebuildRows() {
		persistFilters();
		for (GlassButton row : rows) {
			removeWidget(row);
		}
		rows.clear();
		visible.clear();
		visible.addAll(filtered());
		int y = 104;
		int visibleCount = Math.max(1, (this.height - 168) / 44);
		int start = Math.min(Math.max(scroll, 0), Math.max(0, visible.size() - visibleCount));
		scroll = start;
		int end = Math.min(visible.size(), start + visibleCount);
		for (int i = start; i < end; i++) {
			JsonObject account = visible.get(i);
			String uid = str(account, "uid");
			String name = str(account, "username");
			if (name.isEmpty()) name = uid;
			int rowY = y;
			String selectedName = name;
			GlassButton btn = new GlassButton(64, rowY, this.width - 88, 40, Component.literal(""), false, b -> switchNow(uid, selectedName));
			rows.add(btn);
			addRenderableWidget(btn);
			y += 44;
		}
		lastCount = StenNet.accounts.size();
		lastQuery = query();
		lastPairing = StenNet.pairingCode;
		lastSort = VaultConfig.get().sort;
		lastRank = VaultConfig.get().rankFilter;
		lastCape = capeBox == null ? "" : capeBox.getValue();
		lastMinValue = minValueBox == null ? "" : minValueBox.getValue();
		lastMinDonut = minDonutBox == null ? "" : minDonutBox.getValue();
	}

	private void switchNow(String uid, String name) {
		StenNet.status = "Switching on sten.wtf…";
		StenNet.session.selectedUid = uid;
		StenNet.session.selectedName = name == null ? "" : name;
		StenNet.session.save();
		CompletableFuture.runAsync(() -> {
			try {
				JsonObject minted = StenNet.switchAccount(uid);
				minecraft.execute(() -> {
					try {
						SessionSwap.apply(minted);
						StenNet.status = "Now playing as " + (name == null || name.isEmpty() ? "vault account" : name);
					} catch (Exception ex) {
						StenNet.status = ex.getMessage();
					}
				});
			} catch (Exception ex) {
				StenNet.status = ex.getMessage() == null ? "Switch failed" : ex.getMessage();
			}
		});
	}

	private List<JsonObject> filtered() {
		String q = query();
		VaultConfig cfg = VaultConfig.get();
		String rankWant = cfg.rankFilter == null ? "" : cfg.rankFilter.toLowerCase(Locale.ROOT);
		String capeWant = cfg.capeFilter == null ? "" : cfg.capeFilter.toLowerCase(Locale.ROOT);
		List<JsonObject> out = new ArrayList<>();
		for (JsonObject account : StenNet.accounts) {
			String rank = rankOf(account).toLowerCase(Locale.ROOT);
			String cape = str(account, "capes").toLowerCase(Locale.ROOT);
			String blob = (str(account, "username") + " " + str(account, "uid") + " " + rank + " " + cape).toLowerCase(Locale.ROOT);
			if (!q.isEmpty() && !blob.contains(q)) continue;
			if (!rankWant.isEmpty() && !rank.equals(rankWant) && !rank.contains(rankWant)) continue;
			if (!capeWant.isEmpty() && !cape.contains(capeWant)) continue;
			if (cfg.minValue > 0 && valueOf(account) < cfg.minValue) continue;
			if (cfg.minDonut > 0 && donutNum(account) < cfg.minDonut) continue;
			out.add(account);
		}
		Comparator<JsonObject> cmp = switch (cfg.sort) {
			case "value" -> Comparator.comparingDouble(VaultScreen::valueOf).reversed();
			case "donut" -> Comparator.comparingDouble(VaultScreen::donutNum).reversed();
			case "networth" -> Comparator.comparingDouble(VaultScreen::networthOf).reversed();
			case "level" -> Comparator.comparingDouble(VaultScreen::levelOf).reversed();
			case "capes" -> Comparator.comparingInt(VaultScreen::capeCount).reversed();
			case "name" -> Comparator.comparing(a -> str(a, "username").toLowerCase(Locale.ROOT));
			default -> (a, b) -> 0;
		};
		out.sort(cmp);
		return out;
	}

	private String query() {
		return search == null ? "" : search.getValue().trim().toLowerCase(Locale.ROOT);
	}

	@Override
	public void tick() {
		super.tick();
		if (lastCount != StenNet.accounts.size() || !query().equals(lastQuery) || !StenNet.pairingCode.equals(lastPairing)
			|| !VaultConfig.get().sort.equals(lastSort) || !VaultConfig.get().rankFilter.equals(lastRank)
			|| (capeBox != null && !capeBox.getValue().equals(lastCape))
			|| (minValueBox != null && !minValueBox.getValue().equals(lastMinValue))
			|| (minDonutBox != null && !minDonutBox.getValue().equals(lastMinDonut))) {
			rebuildRows();
		}
	}

	@Override
	public void extractRenderState(GuiGraphicsExtractor graphics, int mouseX, int mouseY, float partialTick) {
		Glass.atmosphere(graphics, this.width, this.height);
		Glass.panel(graphics, 16, 8, this.width - 32, 40, Glass.PANEL_STRONG);
		Glass.wordmark(graphics, this.font, 28, 20);
		graphics.text(this.font, Component.literal("2.1.2"), 28 + this.font.width("sten.wtf") + 8, 20, Glass.CYAN, false);
		if (StenNet.session.signedIn()) {
			String who = StenNet.session.name.isEmpty() ? StenNet.session.userId : StenNet.session.name;
			String playing = StenNet.session.selectedName == null || StenNet.session.selectedName.isBlank()
				? who : (who + "  ·  " + StenNet.session.selectedName);
			graphics.text(this.font, Component.literal(playing), 28, 34, Glass.INK_SOFT, false);
		}

		Glass.panel(graphics, 20, 48, this.width - 40, 26, Glass.PANEL);
		Glass.panel(graphics, 20, 72, 110, 26, Glass.PANEL);
		Glass.panel(graphics, 136, 72, 90, 26, Glass.PANEL);
		Glass.panel(graphics, 232, 72, 90, 26, Glass.PANEL);
		Glass.panel(graphics, 20, this.height - 56, 220, 26, Glass.PANEL);
		super.extractRenderState(graphics, mouseX, mouseY, partialTick);

		int y = 104;
		int shown = Math.max(1, (this.height - 168) / 44);
		int start = Math.min(scroll, Math.max(0, visible.size() - shown));
		int end = Math.min(visible.size(), start + shown);
		for (int i = start; i < end; i++) {
			JsonObject account = visible.get(i);
			UUID uuid = SessionSwap.parseUuid(str(account, "uuid"));
			String name = str(account, "username");
			if (name.isEmpty()) name = str(account, "uid");
			Faces.draw(graphics, this.minecraft, uuid, name, 28, y + 4, 32);
			graphics.text(this.font, Component.literal(name), 72, y + 8, Glass.INK, false);
			String rank = rankOf(account);
			String stats = rank.isEmpty() ? money(valueOf(account)) : (rank + "  ·  " + money(valueOf(account)));
			double nw = networthOf(account);
			double donut = donutNum(account);
			double level = levelOf(account);
			if (level > 1) stats += "  ·  L" + (int) level;
			if (nw > 0) stats += "  ·  NW " + money(nw);
			if (donut > 0) stats += "  ·  D " + money(donut);
			int capes = capeCount(account);
			if (capes > 0) stats += "  ·  " + capes + " capes";
			graphics.text(this.font, Component.literal(stats), 72, y + 24, Glass.INK_SOFT, false);
			y += 44;
		}

		if (!StenNet.pairingCode.isEmpty()) {
			int pw = 300;
			int ph = 118;
			int px = (this.width - pw) / 2;
			int py = (this.height - ph) / 2;
			Glass.panel(graphics, px, py, pw, ph, Glass.PANEL_STRONG);
			graphics.centeredText(this.font, StenNet.officialBuild ? "Official sten.wtf build" : "Type this code on sten.wtf", this.width / 2, py + 18, Glass.INK_SOFT);
			graphics.centeredText(this.font, StenNet.pairingCode, this.width / 2, py + 52, Glass.CYAN);
			graphics.centeredText(this.font, "Keep Minecraft open", this.width / 2, py + 86, Glass.INK_SOFT);
		} else if (!StenNet.session.signedIn()) {
			graphics.centeredText(this.font, "Sign in with sten.wtf to load your vault.", this.width / 2, this.height / 2, Glass.INK_SOFT);
		} else if (StenNet.accounts.isEmpty()) {
			graphics.centeredText(this.font, StenNet.status.isEmpty() ? "No secured accounts in this vault." : StenNet.status, this.width / 2, this.height / 2, Glass.INK_SOFT);
		} else if (visible.isEmpty()) {
			graphics.centeredText(this.font, "No accounts match these filters.", this.width / 2, this.height / 2, Glass.INK_SOFT);
		}

		String footer = StenNet.status;
		if (StenNet.session.signedIn() && !StenNet.accounts.isEmpty()) {
			footer = visible.size() + " / " + StenNet.accounts.size() + (footer.isEmpty() ? "" : "  ·  " + footer);
		}
		if (!footer.isEmpty() && StenNet.pairingCode.isEmpty()) {
			graphics.centeredText(this.font, footer, this.width / 2, this.height - 72, Glass.CYAN);
		}
	}

	@Override
	public boolean mouseScrolled(double mouseX, double mouseY, double scrollX, double scrollY) {
		if (scrollY > 0) scroll = Math.max(0, scroll - 1);
		if (scrollY < 0) scroll++;
		rebuildRows();
		return true;
	}

	@Override
	public void onClose() {
		persistFilters();
		this.minecraft.setScreenAndShow(parent);
	}

	@Override
	public boolean isPauseScreen() {
		return false;
	}

	private static String rankOf(JsonObject account) {
		String rank = str(account, "rank");
		if (!rank.isEmpty()) return rank;
		try {
			if (account.has("stats") && account.get("stats").isJsonObject()) {
				JsonObject stats = account.getAsJsonObject("stats");
				if (stats.has("hypixel") && stats.get("hypixel").isJsonObject()) {
					return str(stats.getAsJsonObject("hypixel"), "rank");
				}
			}
		} catch (Exception ignored) {
		}
		return "";
	}

	private static double valueOf(JsonObject account) {
		try {
			if (account.has("stats") && account.get("stats").isJsonObject()) {
				JsonObject stats = account.getAsJsonObject("stats");
				if (stats.has("totalAccountValue")) return stats.get("totalAccountValue").getAsDouble();
				if (stats.has("hypixel") && stats.get("hypixel").isJsonObject()) {
					JsonObject hypixel = stats.getAsJsonObject("hypixel");
					if (hypixel.has("value") && hypixel.get("value").isJsonObject()) {
						JsonObject value = hypixel.getAsJsonObject("value");
						if (value.has("totalValue")) return value.get("totalValue").getAsDouble();
					}
					if (hypixel.has("value") && hypixel.get("value").isJsonPrimitive()) {
						return hypixel.get("value").getAsDouble();
					}
				}
			}
		} catch (Exception ignored) {
		}
		return 0;
	}

	private static double networthOf(JsonObject account) {
		try {
			JsonObject sky = account.getAsJsonObject("stats").getAsJsonObject("hypixel").getAsJsonObject("skyblock");
			if (sky != null && sky.has("networth")) return sky.get("networth").getAsDouble();
		} catch (Exception ignored) {
		}
		return 0;
	}

	private static double levelOf(JsonObject account) {
		try {
			JsonObject stats = account.getAsJsonObject("stats");
			if (stats.has("LVL")) return stats.get("LVL").getAsDouble();
			JsonObject hypixel = stats.getAsJsonObject("hypixel");
			if (hypixel != null && hypixel.has("networkLevel")) return hypixel.get("networkLevel").getAsDouble();
		} catch (Exception ignored) {
		}
		return 0;
	}

	private static int capeCount(JsonObject account) {
		String capes = str(account, "capes");
		if (capes.isEmpty() || capes.equalsIgnoreCase("None")) return 0;
		int n = 1;
		for (int i = 0; i < capes.length(); i++) {
			if (capes.charAt(i) == ',') n++;
		}
		return n;
	}

	private static double donutNum(JsonObject account) {
		try {
			if (account.has("stats") && account.get("stats").isJsonObject()) {
				JsonObject donut = account.getAsJsonObject("stats").getAsJsonObject("donut");
				if (donut == null) return 0;
				if (donut.has("money")) {
					try {
						return donut.get("money").getAsDouble();
					} catch (Exception ignored) {
						String raw = donut.get("money").getAsString().replace("$", "").replace(",", "");
						return Double.parseDouble(raw);
					}
				}
			}
		} catch (Exception ignored) {
		}
		return 0;
	}

	private static String money(double n) {
		if (n >= 1_000_000_000) return String.format(Locale.US, "$%.2fB", n / 1_000_000_000);
		if (n >= 1_000_000) return String.format(Locale.US, "$%.2fM", n / 1_000_000);
		if (n >= 1000) return String.format(Locale.US, "$%.1fk", n / 1000);
		if (n <= 0) return "$0";
		return String.format(Locale.US, "$%.0f", n);
	}

	private static double parseNum(EditBox box) {
		if (box == null) return 0;
		try {
			String raw = box.getValue().trim().replace("$", "").replace(",", "");
			if (raw.isEmpty()) return 0;
			return Math.max(0, Double.parseDouble(raw));
		} catch (Exception ignored) {
			return 0;
		}
	}

	private static String trimNum(double n) {
		if (n == (long) n) return Long.toString((long) n);
		return String.format(Locale.US, "%.2f", n);
	}

	private static String str(JsonObject obj, String key) {
		if (obj == null || !obj.has(key) || obj.get(key).isJsonNull()) return "";
		try {
			return obj.get(key).getAsString();
		} catch (Exception ignored) {
			return "";
		}
	}
}
