package cn.forevermc.launcher.service;

import cn.forevermc.launcher.bean.FabricAPI;
import cn.forevermc.launcher.bean.Game;
import cn.forevermc.launcher.bean.Page;
import cn.forevermc.launcher.core.LauncherException;
import cn.forevermc.launcher.util.Resources;
import com.google.gson.Gson;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import com.google.gson.reflect.TypeToken;
import com.imyeyu.fx.task.RunAsync;
import com.imyeyu.fx.ui.TimiFXUI;
import com.imyeyu.inject.annotation.Inject;
import com.imyeyu.inject.annotation.InvokeForInjected;
import com.imyeyu.inject.annotation.Service;
import com.imyeyu.io.IO;
import com.imyeyu.io.JarReader;
import com.imyeyu.java.TimiJava;
import com.imyeyu.java.bean.timi.TimiCode;
import com.imyeyu.java.bean.timi.TimiException;
import com.imyeyu.network.FileRequest;
import com.imyeyu.network.TimiRequest;
import com.imyeyu.utils.OS;
import javafx.beans.property.MapProperty;
import javafx.beans.property.ReadOnlyMapProperty;
import javafx.beans.property.SimpleMapProperty;
import javafx.collections.FXCollections;
import lombok.Cleanup;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.apache.hc.client5.http.fluent.Request;
import org.apache.hc.core5.http.ContentType;

import java.io.File;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * ForeverMC 账号验证服务
 *
 * @author 夜雨
 * @since 2022-11-30 00:31
 */
@Slf4j
@Service
public class FMCLoginService implements OS.FileSystem {

	/**
	 * 登录玩家
	 *
	 * @author 夜雨
	 * @since 2024-05-21 18:11
	 */
	@Data
	public static class MinecraftPlayer {

		/** 账号 ID */
		Long id;

		/** 玩家名 */
		String name;

		/** 上次登录时间 */
		Long lastLoginAt;
	}

	/**
	 * 令牌请求
	 *
	 * @author 夜雨
	 * @since 2024-05-21 18:20
	 */
	@Data
	public static class TokenRequest {

		/** 账号（ID、邮箱或玩家名） */
		String user;

		/** 密码 */
		String password;
	}

	/**
	 * 令牌返回
	 *
	 * @author 夜雨
	 * @since 2024-05-21 18:19
	 */
	@Data
	public static class TokenResponse {

		/** 令牌 */
		String token;

		/** 过期于 */
		long expiredAt;
	}

	private static final String SETTING_API = Resources.TIMI_SERVER_API + "/setting";

	private static final String MIRROR_API = Resources.TIMI_SERVER_API + "/mirror";

	private static final String ATTACH_DL_API = Resources.TIMI_SERVER_API + "/attachment/download";

	/** Timi 数据中心验证接口 */
	private static final String LOGIN_API = Resources.TIMI_SERVER_API + "/fmc/player/login";

	/** Timi 数据中心验证接口 */
	private static final String LIST_PLAYER_API = Resources.TIMI_SERVER_API + "/fmc/player/list";

	/** Timi 数据中心验证接口 */
	private static final String LOGIN_TOKEN_API = Resources.TIMI_SERVER_API + "/fmc/player/login/token";

	@Inject
	private Gson gson;

	@Inject
	private GameService gameService;

	/** 辅助登录模组映射 Map&lt;版本, mongoId&gt; */
	private static final MapProperty<String, String> loginModMap = new SimpleMapProperty<>(FXCollections.observableHashMap());

	/** FabricAPI 镜像映射 Map&lt;版本, FabricAPI&gt; */
	private static final MapProperty<String, FabricAPI> fabricApiMap = new SimpleMapProperty<>(FXCollections.observableHashMap());

	@InvokeForInjected
	private void injected() {
		new RunAsync<Void>() {

			Map<String, String> loginModMapTemp = null;

			Map<String, FabricAPI> fabricAPIMapTemp = null;

			@Override
			protected Void call() throws Exception {
				{
					String data = TimiRequest.<String>get(SETTING_API + "/FMC_LOGIN_FABRIC").result();
					loginModMapTemp = gson.fromJson(data, new TypeToken<Map<String, String>>() {}.getType());
				}
				{
					List<FabricAPI> data = TimiRequest.<List<FabricAPI>>get(MIRROR_API + "/FabricAPI").result();
					fabricAPIMapTemp = data.stream().collect(Collectors.toMap(FabricAPI::getMinecraftVer, item -> item));
				}
				return null;
			}

			@Override
			protected void onFinish(Void map) {
				fabricApiMap.clear();
				fabricApiMap.putAll(fabricAPIMapTemp);
				loginModMap.clear();
				loginModMap.putAll(loginModMapTemp);
			}

			@Override
			protected void onException(Throwable e) {
				log.error("list support login mod map error", e);
			}
		}.start();
	}

	public boolean isSupportFMCLogin(Game game) {
		if (game == null || game.isLegacy()) {
			return false;
		}
		if (loginModMap.isEmpty()) {
			return false;
		}
		Game.MetaData.RealVersion realVersion = gameService.getRealVersion(game);

		boolean supportLoginMod = loginModMap.containsKey(realVersion.getValue());
		try {
			if (!supportLoginMod) {
				return false;
			}
			if (hasMod(game, "fabric-api")) {
				return true;
			}
			for (String key : fabricApiMap.keySet()) {
				if (key.contains(realVersion.getValue())) {
					return true;
				}
			}
			return false;
		} catch (Exception e) {
			log.error("check token login support error", e);
			return false;
		}
	}

	public void checkLoginMod(Game game) {
		try {
			Game.MetaData.RealVersion realVersion = gameService.getRealVersion(game);
			if (!loginModMap.containsKey(realVersion.getValue())) {
				// 不支持的版本
				throw new LauncherException(Page.MENU, TimiFXUI.MULTILINGUAL.text("fmc.launcher.account.not_support"));
			}
			File modPath = IO.dir(IO.fitPath(game.getPath().getAbsolutePath()) + "mods" + SEP);
			if (!hasMod(game, "fmc-login-fabric")) {
				log.info("downloading fmc-login-fabric mod for {}", loginModMap.get(realVersion.getValue()));
				String dlURL = ATTACH_DL_API + "/%s".formatted(loginModMap.get(realVersion.getValue()));
				FileRequest.get(dlURL).toFile(modPath.getAbsolutePath(), "fmc-login-fabric.jar");
			}
		} catch (Exception e) {
			log.error("check mod error", e);
			throw new TimiException(TimiCode.RESULT_BAD, "check mod fail");
		}
	}

	public TokenResponse genLoginToken(String user, String password) throws Exception {
		TokenRequest request = new TokenRequest();
		request.setUser(user);
		request.setPassword(password);
		return TimiRequest.<TokenResponse>wrap(Request.post(LOGIN_TOKEN_API).bodyString(gson.toJson(request), ContentType.APPLICATION_JSON)).result();
	}

	public List<MinecraftPlayer> listBoundPlayer(String token) throws Exception {
		return TimiRequest.<List<MinecraftPlayer>>wrap(Request.get(LIST_PLAYER_API).addHeader("Token", token)).result();
	}

	public TokenResponse doLogin(MinecraftPlayer player, String token) throws Exception {
		Map<String, Object> map = new HashMap<>();
		map.put("playerId", player.id);
		return TimiRequest.<TokenResponse>wrap(Request.get(LIST_PLAYER_API)
		                                              .addHeader("Token", token)
		                                              .bodyString(gson.toJson(map), ContentType.APPLICATION_JSON)).result();
	}

	private boolean hasMod(Game game, String name) {
		try {
			File modPath = IO.dir(IO.fitPath(game.getPath().getAbsolutePath()) + "mods" + SEP);
			File[] mods = modPath.listFiles();
			if (TimiJava.isNotEmpty(mods)) {
				for (int i = 0; i < mods.length; i++) {
					if (mods[i].getName().endsWith(".jar")) {
						@Cleanup
						JarReader jar = new JarReader(mods[i]);
						if (jar.has("fabric.mod.json")) {
							String fabricJson = new String(jar.getBytes("fabric.mod.json"));
							JsonObject root = JsonParser.parseString(fabricJson).getAsJsonObject();
							if (root.get("id").getAsString().equals(name)) {
								return true;
							}
						}
					}
				}
			}
			return false;
		} catch (Exception e) {
			log.error("check mod error", e);
			throw new TimiException(TimiCode.ERROR, "check mod fail");
		}
	}

	public static ReadOnlyMapProperty<String, String> loginModMapProperty() {
		return loginModMap;
	}

	public static ReadOnlyMapProperty<String, FabricAPI> fabricAPIMapProperty() {
		return fabricApiMap;
	}
}
