using System;
using System.IO;
using System.Linq;
using System.Security.Cryptography;
using System.Security.Cryptography.Pkcs;
using System.Security.Cryptography.X509Certificates;
using Org.BouncyCastle.OpenSsl;
using Valg.EVA.Skanning.Configuration;
using Valg.EVA.Skanning.Diagnostics;

namespace Valg.EVA.Skanning.Signing
{
    public class PemSignatureValidator : IPemSignatureValidator
    {
        private readonly IApplicationConfigurationReader applicationConfigurationReader;
        private readonly Oid Sha256 = new Oid("2.16.840.1.101.3.4.2.1");

        public PemSignatureValidator(IApplicationConfigurationReader applicationConfigurationReader)
        {
            this.applicationConfigurationReader = applicationConfigurationReader;
        }

        public SignatureValidationResult ValidateDocumentSignature(byte[] document, byte[] documentSignature)
        {
            var result = new SignatureValidationResult { IsValid = true };

            VerifyData(document, documentSignature, result);

            return result;
        }

        private void VerifyData(byte[] dataToVerify, byte[] signatureRaw, SignatureValidationResult result)
        {
            var signature = ReadPemSignature(signatureRaw);
            var contentInfo = new ContentInfo(Sha256, dataToVerify);

            var signedCms = new SignedCms(contentInfo, true);
            signedCms.Decode(signature);

            if (!IsSignedWithProductionCertificate(signedCms))
            {
                var certificateUsedWhenSigning = GetCertificateUsedWhenSigning(signedCms);

                if (certificateUsedWhenSigning == null)
                {
                    result.IsValid = false;
                    result.IsRootCertificateMissing = true;
                }
                else
                {
                    try
                    {
                        signedCms.CheckSignature(new X509Certificate2Collection(certificateUsedWhenSigning), verifySignatureOnly: true);
                    }
                    catch (CryptographicException c)
                    {
                        result.IsValid = false;
                        EvaSkanningEventSource.Log.SignatureCheckFailedUsingTestCertificate(c.ToString());
                    }
                }
            }
        }

        private bool IsSignedWithProductionCertificate(SignedCms signedCms)
        {
            var result = false;
            Exception exception = null;
            try
            {
                signedCms.CheckSignature(false);
                var signerInfo = signedCms.SignerInfos.Cast<SignerInfo>().FirstOrDefault();
                if (signerInfo != null)
                {
                    if (signerInfo.Certificate.Thumbprint == GetProductionCertificateThumbprint())
                    {
                        result = true;
                    }
                }
            }
            catch (CryptographicException cex)
            {
                exception = cex;
            }

            if (!result)
            {
                var exceptionText = "No exception";
                if (exception != null)
                {
                    exceptionText = exception.ToString();
                }

                EvaSkanningEventSource.Log.SignatureCheckFailedUsingProductionCertificate(exceptionText);
            }

            return result;
        }

        private string GetProductionCertificateThumbprint()
        {
            return applicationConfigurationReader.GetAppSetting("ProductionCertificateThumbprint");
        }

        private byte[] ReadPemSignature(byte[] pemSignature)
        {
            using (var memoryStream = new MemoryStream(pemSignature))
            using (var stream = new StreamReader(memoryStream))
            {
                var reader = new PemReader(stream);
                var contentInfo = (Org.BouncyCastle.Asn1.Cms.ContentInfo)reader.ReadObject();
                return contentInfo.GetEncoded();
            }
        }

        private static X509Certificate2 GetCertificateUsedWhenSigning(SignedCms signedCms)
        {
            var firstMatchingCertificate = (signedCms.SignerInfos.Cast<SignerInfo>()
                .Select(signInfo => GetTimeValidCertificateFromStore(StoreName.Root, StoreLocation.CurrentUser, signInfo.Certificate.Thumbprint, X509FindType.FindByThumbprint)))
                .FirstOrDefault(x509Cert => x509Cert != null);

            return firstMatchingCertificate;
        }

        private static X509Certificate2 GetTimeValidCertificateFromStore(StoreName storeName, StoreLocation storeLocation, string searchExpression, X509FindType x509FindType)
        {
            var x509Store = new X509Store(storeName, storeLocation);
            try
            {
                x509Store.Open(OpenFlags.ReadOnly);
                var validCertificates = x509Store.Certificates.Find(X509FindType.FindByTimeValid, DateTime.Now, false);
                var matchingCertificates = validCertificates.Find(x509FindType, searchExpression, false);
                return matchingCertificates.Cast<X509Certificate2>().FirstOrDefault();
            }
            finally
            {
                x509Store.Close();
            }
        }
    }
}
