/*
 * Copyright (c) 2025 ETH Zürich, IT Services
 * 
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 */

using System;
using System.Security.Cryptography;
using System.Text;
using System.Threading;
using SafeExamBrowser.Configuration.Contracts;
using SafeExamBrowser.Configuration.Contracts.Cryptography;
using SafeExamBrowser.Configuration.Contracts.Integrity;
using SafeExamBrowser.Logging.Contracts;

namespace SafeExamBrowser.Configuration.Cryptography
{
	public class KeyGenerator : IKeyGenerator
	{
		private readonly object @lock = new object();

		private readonly ThreadLocal<SHA256Managed> algorithm;
		private readonly AppConfig appConfig;
		private readonly IIntegrityModule integrityModule;
		private readonly ILogger logger;

		private string browserExamKey;

		public KeyGenerator(AppConfig appConfig, IIntegrityModule integrityModule, ILogger logger)
		{
			this.algorithm = new ThreadLocal<SHA256Managed>(() => new SHA256Managed());
			this.appConfig = appConfig;
			this.integrityModule = integrityModule;
			this.logger = logger;
		}

		public string CalculateAppSignatureKey(string connectionToken, string salt)
		{
			if (integrityModule.TryCalculateAppSignatureKey(connectionToken, salt, out var appSignatureKey))
			{
				logger.Debug("Successfully calculated app signature key using integrity module.");
			}
			else
			{
				logger.Error("Failed to calculate app signature key using integrity module!");
			}

			return appSignatureKey;
		}

		public string CalculateBrowserExamKeyHash(string configurationKey, byte[] salt, string url)
		{
			var urlWithoutFragment = url.Split('#')[0];
			var hash = algorithm.Value.ComputeHash(Encoding.UTF8.GetBytes(urlWithoutFragment + (browserExamKey ?? ComputeBrowserExamKey(configurationKey, salt))));
			var key = ToString(hash);

			return key;
		}

		public string CalculateConfigurationKeyHash(string configurationKey, string url)
		{
			var urlWithoutFragment = url.Split('#')[0];
			var hash = algorithm.Value.ComputeHash(Encoding.UTF8.GetBytes(urlWithoutFragment + configurationKey));
			var key = ToString(hash);

			return key;
		}

		public void UseCustomBrowserExamKey(string browserExamKey)
		{
			if (browserExamKey != default)
			{
				this.browserExamKey = browserExamKey;
				logger.Debug("Initialized custom browser exam key.");
			}
		}

		private string ComputeBrowserExamKey(string configurationKey, byte[] salt)
		{
			lock (@lock)
			{
				if (browserExamKey == default)
				{
					logger.Debug("Initializing browser exam key...");

					if (configurationKey == default)
					{
						configurationKey = "";
						logger.Warn("The current configuration does not contain a value for the configuration key!");
					}

					if (salt == default || salt.Length == 0)
					{
						salt = new byte[0];
						logger.Warn("The current configuration does not contain a salt value for the browser exam key!");
					}

					if (integrityModule.TryCalculateBrowserExamKey(configurationKey, ToString(salt), out browserExamKey))
					{
						logger.Debug("Successfully calculated browser exam key using integrity module.");
					}
					else
					{
						logger.Warn("Failed to calculate browser exam key using integrity module! Falling back to simplified calculation...");

						using (var algorithm = new HMACSHA256(salt))
						{
							var hash = algorithm.ComputeHash(Encoding.UTF8.GetBytes(appConfig.CodeSignatureHash + appConfig.ProgramBuildVersion + configurationKey));
							var key = ToString(hash);

							browserExamKey = key;
						}

						logger.Debug("Successfully calculated browser exam key using simplified calculation.");
					}
				}
			}

			return browserExamKey;
		}

		private string ToString(byte[] bytes)
		{
			return BitConverter.ToString(bytes).ToLower().Replace("-", string.Empty);
		}
	}
}
