using NetMQ;
using Phantom.Common.Data.Agent;
using Phantom.Common.Logging;
using Phantom.Utils.Cryptography;
using Phantom.Utils.IO;
using Serilog;

namespace Phantom.Server;

static class CertificateFiles {
	private static ILogger Logger { get; } = PhantomLogger.Create(nameof(CertificateFiles));

	private const string SecretKeyFileName = "secret.key";
	private const string AgentKeyFileName = "agent.key";

	public static async Task<(NetMQCertificate, AgentAuthToken)?> CreateOrLoad(string folderPath) {
		string secretKeyFilePath = Path.Combine(folderPath, SecretKeyFileName);
		string agentKeyFilePath = Path.Combine(folderPath, AgentKeyFileName);

		bool secretKeyFileExists = File.Exists(secretKeyFilePath);
		bool agentKeyFileExists = File.Exists(agentKeyFilePath);

		if (secretKeyFileExists && agentKeyFileExists) {
			try {
				return await LoadCertificatesFromFiles(secretKeyFilePath, agentKeyFilePath);
			} catch (IOException e) {
				Logger.Fatal("Error reading certificate files.");
				Logger.Fatal(e.Message);
				return null;
			} catch (Exception) {
				Logger.Fatal("Certificate files contain invalid data.");
				return null;
			}
		}

		if (secretKeyFileExists || agentKeyFileExists) {
			string existingKeyFilePath = secretKeyFileExists ? secretKeyFilePath : agentKeyFilePath;
			string missingKeyFileName = secretKeyFileExists ? AgentKeyFileName : SecretKeyFileName;
			Logger.Fatal("The certificate file {ExistingKeyFilePath} exists but {MissingKeyFileName} does not. Please delete it to regenerate both certificate files.", existingKeyFilePath, missingKeyFileName);
			return null;
		}

		Logger.Information("Creating certificate files in: {FolderPath}", folderPath);
		
		try {
			return await GenerateCertificateFiles(secretKeyFilePath, agentKeyFilePath);
		} catch (Exception e) {
			Logger.Fatal("Error creating certificate files.");
			Logger.Fatal(e.Message);
			return null;
		}
	}

	private static async Task<(NetMQCertificate, AgentAuthToken)?> LoadCertificatesFromFiles(string secretKeyFilePath, string agentKeyFilePath) {
		byte[] secretKey = await ReadCertificateFile(secretKeyFilePath);
		byte[] agentKey = await ReadCertificateFile(agentKeyFilePath);

		var (publicKey, agentToken) = AgentKeyData.FromBytes(agentKey);
		var certificate = new NetMQCertificate(secretKey, publicKey);
		
		LogAgentConnectionInfo("Loaded existing certificate files.", agentKeyFilePath, agentKey);
		return (certificate, agentToken);
	}

	private static Task<byte[]> ReadCertificateFile(string filePath) {
		Files.RequireMaximumFileSize(filePath, 64);
		return File.ReadAllBytesAsync(filePath);
	}

	private static async Task<(NetMQCertificate, AgentAuthToken)> GenerateCertificateFiles(string secretKeyFilePath, string agentKeyFilePath) {
		var certificate = new NetMQCertificate();
		var agentToken = AgentAuthToken.Generate();
		var agentKey = AgentKeyData.ToBytes(certificate.PublicKey, agentToken);

		await Files.WriteBytesAsync(secretKeyFilePath, certificate.SecretKey, FileMode.Create, Chmod.URW_GR);
		await Files.WriteBytesAsync(agentKeyFilePath, agentKey, FileMode.Create, Chmod.URW_GR);

		LogAgentConnectionInfo("Created new certificate files.", agentKeyFilePath, agentKey);
		return (certificate, agentToken);
	}

	private static void LogAgentConnectionInfo(string message, string agentKeyFilePath, byte[] agentKey) {
		Logger.Information(message + " Agents will need the agent key to connect.");
		Logger.Information("Agent key file: {AgentKeyFilePath}", agentKeyFilePath);
		Logger.Information("Agent key: {AgentKey}", TokenGenerator.EncodeBytes(agentKey));
	}
}