/*
 * Copyright (c) 2024 lax1dude. All Rights Reserved.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 *
 */

package net.lax1dude.eaglercraft.v1_8.update;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import net.lax1dude.eaglercraft.v1_8.EagRuntime;
import net.lax1dude.eaglercraft.v1_8.EaglercraftVersion;
import net.lax1dude.eaglercraft.v1_8.internal.PlatformApplication;
import net.lax1dude.eaglercraft.v1_8.internal.PlatformUpdateSvc;
import net.lax1dude.eaglercraft.v1_8.log4j.LogManager;
import net.lax1dude.eaglercraft.v1_8.log4j.Logger;

public class UpdateService {

	private static class RawKnownCertHolder {

		private final byte[] data;
		private final int hashcode;
		private final long age;

		public RawKnownCertHolder(byte[] data) {
			this.data = data;
			this.hashcode = Arrays.hashCode(data);
			this.age = EagRuntime.steadyTimeMillis();
		}

		@Override
		public boolean equals(Object o) {
			return o != null && (o == this
					|| ((o instanceof RawKnownCertHolder) && Arrays.equals(((RawKnownCertHolder) o).data, data)));
		}

		@Override
		public int hashCode() {
			return hashcode;
		}
	}

	private static final Logger logger = LogManager.getLogger("UpdateService");
	private static UpdateCertificate myUpdateCert = null;

	private static boolean isBundleDataValid = false;
	private static UpdateCertificate latestUpdateFound = null;
	private static final Set<UpdateCertificate> availableUpdates = new HashSet<>();
	private static final Set<RawKnownCertHolder> fastUpdateKnownCheckSet = new HashSet<>();

	private static final Set<UpdateCertificate> dismissedUpdates = new HashSet<>();

	public static void addCertificateToSet(byte[] certificateData) {
		addCertificateToSet(certificateData, true);
	}

	private static void addCertificateToSet(byte[] certificateData, boolean saveLatest) {
		if (EagRuntime.getConfiguration().allowUpdateDL()) {
			synchronized (availableUpdates) {
				try {
					if (certificateData.length > 32767) {
						throw new CertificateInvalidException(
								"Certificate is too large! (" + certificateData.length + " bytes)");
					}
					if (!fastUpdateKnownCheckSet.add(new RawKnownCertHolder(certificateData))) {
						if (EagRuntime.getConfiguration().isLogInvalidCerts()) {
							logger.info("Ignoring {} byte certificate that has already been processed",
									certificateData.length);
						}
						freeMemory();
						return;
					}
					UpdateCertificate cert = UpdateCertificate.parseAndVerifyCertificate(certificateData);
					if (EaglercraftVersion.updateBundlePackageName.equalsIgnoreCase(cert.bundlePackageName)) {
						if (myUpdateCert == null || !Arrays.equals(cert.bundleDataHash, myUpdateCert.bundleDataHash)) {
							if (availableUpdates.add(cert)) {
								logger.info("Found new update: {} - {}", cert.bundleDisplayName,
										cert.bundleDisplayVersion);
								if (cert.bundleVersionInteger > EaglercraftVersion.updateBundlePackageVersionInt
										&& (latestUpdateFound == null
												|| cert.bundleVersionInteger > latestUpdateFound.bundleVersionInteger
												|| (cert.bundleVersionInteger == latestUpdateFound.bundleVersionInteger
														&& cert.sigTimestamp > latestUpdateFound.sigTimestamp))
										&& !dismissedUpdates.contains(cert)) {
									latestUpdateFound = cert;
									if (saveLatest) {
										PlatformApplication.setLocalStorage(
												EaglercraftVersion.updateLatestLocalStorageKey, certificateData, false);
									}
								}
							} else if (EagRuntime.getConfiguration().isLogInvalidCerts()) {
								logger.info("Ignoring already indexed update: {} - {}", cert.bundleDisplayName,
										cert.bundleDisplayVersion);
							}
						}
					} else {
						if (EagRuntime.getConfiguration().isLogInvalidCerts()) {
							logger.warn("Ignoring 3rd party update certificate: {} - {} ({})", cert.bundleDisplayName,
									cert.bundleDisplayVersion, cert.bundlePackageName);
							logger.warn("Note: the certificate still had a valid signature (leaked private key?!)");
						}
					}
				} catch (Throwable t) {
					if (EagRuntime.getConfiguration().isLogInvalidCerts()) {
						logger.error(
								"Invalid update certificate recieved! The certificate may be from a different client");
						logger.error(t);
					}
				}
			}
		}
	}

	public static void dismiss(UpdateCertificate cert) {
		if (latestUpdateFound == cert) {
			latestUpdateFound = null;
		}
		dismissedUpdates.add(cert);
	}

	private static void freeMemory() {
		if (fastUpdateKnownCheckSet.size() > 127) {
			List<RawKnownCertHolder> lst = new ArrayList<>(fastUpdateKnownCheckSet);
			fastUpdateKnownCheckSet.clear();
			lst.sort((c1, c2) -> {
				return (int) (c2.age - c1.age);
			});
			for (int i = 0; i < 64; ++i) {
				fastUpdateKnownCheckSet.add(lst.get(i));
			}
		}
	}

	public static Collection<UpdateCertificate> getAvailableUpdates() {
		return availableUpdates;
	}

	public static byte[] getClientBundleData() {
		if (isBundleDataValid) {
			return PlatformUpdateSvc.getClientBundleData();
		}
		return null;
	}

	public static UpdateCertificate getClientCertificate() {
		return myUpdateCert;
	}

	public static byte[] getClientSignatureData() {
		if (myUpdateCert != null) {
			return PlatformUpdateSvc.getClientSignatureData();
		}
		return null;
	}

	public static UpdateCertificate getLatestUpdateFound() {
		return latestUpdateFound;
	}

	public static UpdateResultObj getUpdateResult() {
		return PlatformUpdateSvc.getUpdateResult();
	}

	public static UpdateProgressStruct getUpdatingStatus() {
		return PlatformUpdateSvc.getUpdatingStatus();
	}

	public static void initialize() {
		if (!supported()) {
			logger.info("Update service is not supported on this client");
			return;
		}
		PlatformUpdateSvc.initialize();
		if (PlatformUpdateSvc.getClientSignatureData() != null) {
			logger.info("Testing client update certificate...");
			try {
				myUpdateCert = UpdateCertificate.parseAndVerifyCertificate(PlatformUpdateSvc.getClientSignatureData());
				if (!EaglercraftVersion.updateBundlePackageName.equalsIgnoreCase(myUpdateCert.bundlePackageName)) {
					throw new CertificateInvalidException(
							"Certificate package name does not match current client package name!");
				}
				if (EaglercraftVersion.updateBundlePackageVersionInt != myUpdateCert.bundleVersionInteger) {
					throw new CertificateInvalidException(
							"Certificate client version does not match current client version!");
				}
			} catch (Throwable t) {
				myUpdateCert = null;
				logger.error("Client update certificate is invalid!");
				logger.error(t);
			}
			if (myUpdateCert != null && PlatformUpdateSvc.getClientBundleData() != null) {
				isBundleDataValid = myUpdateCert.isBundleDataValid(PlatformUpdateSvc.getClientBundleData());
				if (!isBundleDataValid) {
					logger.error(
							"Client checksum does not match certificate! \"Download Offline\" button will download a fresh client");
				}
			}
		}
		byte[] latestUpdate = PlatformApplication.getLocalStorage(EaglercraftVersion.updateLatestLocalStorageKey,
				false);
		if (latestUpdate != null) {
			addCertificateToSet(latestUpdate, false);
		}
	}

	public static void installSignedClient(UpdateCertificate clientCert, byte[] clientPayload, boolean setDefault,
			boolean setTimeout) {
		PlatformUpdateSvc.installSignedClient(clientCert, clientPayload, setDefault, setTimeout);
	}

	public static void quine() {
		if (myUpdateCert != null) {
			byte[] data = getClientBundleData();
			if (data != null) {
				logger.info("Generating signed offline download...");
				PlatformUpdateSvc.quine(myUpdateCert, data);
			} else {
				logger.error("Client checksum does not match certificate! Downloading a fresh client...");
				PlatformUpdateSvc.startClientUpdateFrom(myUpdateCert);
			}
		}
	}

	public static void quine(UpdateCertificate cert, byte[] payload) {
		PlatformUpdateSvc.quine(cert, payload);
	}

	public static boolean shouldDisableDownloadButton() {
		return EagRuntime.getConfiguration().getDownloadOfflineButtonLink() == null && (myUpdateCert == null
				|| (getClientBundleData() == null && PlatformUpdateSvc.getUpdatingStatus().isBusy));
	}

	public static void startClientUpdateFrom(UpdateCertificate clientUpdate) {
		PlatformUpdateSvc.startClientUpdateFrom(clientUpdate);
	}

	public static boolean supported() {
		return EaglercraftVersion.enableUpdateService && EagRuntime.getConfiguration().allowUpdateSvc()
				&& PlatformUpdateSvc.supported();
	}
}