using System;
using System.IO;
using System.Security.Cryptography.X509Certificates;
using Kyoo.Authentication.Models;
using Microsoft.Extensions.DependencyInjection;
using Org.BouncyCastle.Asn1.X509;
using Org.BouncyCastle.Crypto;
using Org.BouncyCastle.Crypto.Generators;
using Org.BouncyCastle.Crypto.Operators;
using Org.BouncyCastle.Math;
using Org.BouncyCastle.Pkcs;
using Org.BouncyCastle.Security;
using Org.BouncyCastle.Utilities;
using Org.BouncyCastle.X509;
using X509Certificate = Org.BouncyCastle.X509.X509Certificate;
namespace Kyoo.Authentication
{
	/// 
	/// A class containing multiple extensions methods to manage certificates.
	/// 
	public static class Certificates
	{
		/// 
		/// Add the certificate file to the identity server. If the certificate will expire soon, automatically renew it.
		/// If no certificate exists, one is generated. 
		/// 
		/// The identity server that will be modified.
		/// The certificate options
		/// 
		public static IIdentityServerBuilder AddSigninKeys(this IIdentityServerBuilder builder, 
			CertificateOption options)
		{
			X509Certificate2 certificate = GetCertificate(options);
			builder.AddSigningCredential(certificate);
			
			if (certificate.NotAfter.AddDays(7) <= DateTime.UtcNow)
			{
				Console.WriteLine("Signin certificate will expire soon, renewing it.");
				if (File.Exists(options.OldFile))
					File.Delete(options.OldFile);
				File.Move(options.File, options.OldFile);
				builder.AddValidationKey(GenerateCertificate(options.File, options.Password));
			}
			else if (File.Exists(options.OldFile))
				builder.AddValidationKey(GetExistingCredential(options.OldFile, options.Password));
			return builder;
		}
		/// 
		/// Get or generate the sign-in certificate.
		/// 
		/// The certificate options
		/// A valid certificate
		private static X509Certificate2 GetCertificate(CertificateOption options)
		{
			return File.Exists(options.File) 
				? GetExistingCredential(options.File, options.Password) 
				: GenerateCertificate(options.File, options.Password);
		}
		/// 
		/// Load a certificate from a file
		/// 
		/// The path of the certificate
		/// The password of the certificate
		/// The loaded certificate
		private static X509Certificate2 GetExistingCredential(string file, string password)
		{
			return new(file, password,
				X509KeyStorageFlags.MachineKeySet |
				X509KeyStorageFlags.PersistKeySet |
				X509KeyStorageFlags.Exportable
			);
		}
		/// 
		/// Generate a new certificate key and put it in the file at .
		/// 
		/// The path of the output file
		/// The password of the new certificate
		/// The generated certificate
		private static X509Certificate2 GenerateCertificate(string file, string password)
		{
			SecureRandom random = new();
			
			X509V3CertificateGenerator certificateGenerator = new();
			certificateGenerator.SetSerialNumber(BigIntegers.CreateRandomInRange(BigInteger.One, 
				BigInteger.ValueOf(long.MaxValue), random));
			certificateGenerator.SetIssuerDN(new X509Name($"C=NL, O=SDG, CN=Kyoo"));
			certificateGenerator.SetSubjectDN(new X509Name($"C=NL, O=SDG, CN=Kyoo"));
			certificateGenerator.SetNotBefore(DateTime.UtcNow.Date);
			certificateGenerator.SetNotAfter(DateTime.UtcNow.Date.AddMonths(3));
 
			KeyGenerationParameters keyGenerationParameters = new(random, 2048);
			RsaKeyPairGenerator keyPairGenerator = new();
			keyPairGenerator.Init(keyGenerationParameters);
 
			AsymmetricCipherKeyPair subjectKeyPair = keyPairGenerator.GenerateKeyPair();
			certificateGenerator.SetPublicKey(subjectKeyPair.Public);
			const string signatureAlgorithm = "MD5WithRSA";
			Asn1SignatureFactory signatureFactory = new(signatureAlgorithm, subjectKeyPair.Private);
			X509Certificate bouncyCert = certificateGenerator.Generate(signatureFactory);
			Pkcs12Store store = new Pkcs12StoreBuilder().Build();
			store.SetKeyEntry("Kyoo_key", new AsymmetricKeyEntry(subjectKeyPair.Private), new []
			{
				new X509CertificateEntry(bouncyCert)
			});
			using MemoryStream pfxStream = new();
			store.Save(pfxStream, password.ToCharArray(), random);
			X509Certificate2 certificate = new(pfxStream.ToArray(), password, X509KeyStorageFlags.Exportable);
			using FileStream fileStream = File.OpenWrite(file);
			pfxStream.WriteTo(fileStream);
			return certificate;
		}
	}
}