using GostCryptography.Config;
using GostCryptography.Gost_R3410;
using GostCryptography.Gost_R3411;
using GostXades.Abstractions;
using GostXades.Helpers;
using Microsoft.Xades;
using Org.BouncyCastle.X509;
using System;
using System.Collections.Generic;
using System.Security;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using System.Security.Cryptography.Xml;

namespace GostXades
{
    public class GostCryptoProvider : ICryptoProvider
    {
        public GostCryptoProvider(CryptoProviderTypeEnum cryptoProviderType)
        {
            GostCryptoConfig.ProviderType = (GostCryptography.Base.ProviderType)cryptoProviderType;
        }

        private Dictionary<string, string> HashAlgorithmMap { get; set; } = new Dictionary<string, string>
        {
            ["http://www.w3.org/2001/04/xmldsig-more#gostr3411"] = "GOST3411"
        };

        public HashAlgorithm GetHashAlgorithm(string algorithm)
        {
            string algorithmName;
            var algorithmUrl = algorithm;
            if (!HashAlgorithmMap.TryGetValue(algorithmUrl, out algorithmName))
            {
                return null;
            }
            var pkHash = HashAlgorithm.Create(algorithmName);
            return pkHash;
        }

        private int _referenceIndex;

        public string SignatureMethod => Gost_R3410_2012_256_AsymmetricAlgorithm.SignatureAlgorithmValue;
        public string DigestMethod => Gost_R3411_2012_256_HashAlgorithm.AlgorithmNameValue;
        private HashAlgorithm GetHashAlgorithm() => new Gost_R3411_2012_256_HashAlgorithm();

        public AsymmetricAlgorithm GetAsymmetricAlgorithm(X509Certificate2 certificate, string privateKeyPassword)
        {
            var provider = certificate.GetPrivateKeyAlgorithm();

            if (!string.IsNullOrEmpty(privateKeyPassword))
            {
                var secureString = new SecureString();
                foreach (var chr in privateKeyPassword)
                    secureString.AppendChar(chr);

                if (provider is Gost_R3410_2012_256_AsymmetricAlgorithm alg1)
                    alg1.SetContainerPassword(secureString);
                else if (provider is Gost_R3410_2012_512_AsymmetricAlgorithm alg2)
                    alg2.SetContainerPassword(secureString);
                else throw new NotSupportedException(
                    $"Неизвестный тип алгоритма {provider.SignatureAlgorithm} для применения пароля");
            }

            return provider;
        }

        public Reference GetReference(string signedElementId, string signatureId)
        {
            var reference = new Reference
            {
                Uri = $"#{signedElementId}",
                DigestMethod = DigestMethod,
                Id = $"{signatureId}-ref{_referenceIndex}"
            };
            reference.AddTransform(new XmlDsigEnvelopedSignatureTransform());
            reference.AddTransform(new XmlDsigExcC14NTransform());

            _referenceIndex++;

            return reference;
        }

        public AsymmetricSignatureFormatter GetSignatureFormatter(X509Certificate2 certificate)
        {
            var alg = certificate.GetPrivateKeyAlgorithm();
            var signatureDescription =
                (SignatureDescription)CryptoConfig.CreateFromName(alg.SignatureAlgorithm);
            return signatureDescription.CreateFormatter(alg);
        }

        public XadesObject GetXadesObject(XadesInfo xadesInfo, string signatureId)
        {
            var xadesObject = new XadesObject
            {
                QualifyingProperties = new QualifyingProperties
                {
                    Target = $"#{signatureId}",
                    SignedProperties = new SignedProperties { Id = $"{signatureId}-signedprops" }
                }
            };

            var hashAlgorithm = GetHashAlgorithm();
            var hashValue = hashAlgorithm.ComputeHash(xadesInfo.RawCertData);

            var x509CertificateParser = new X509CertificateParser();
            var bouncyCert = x509CertificateParser.ReadCertificate(xadesInfo.RawCertData);

            var cert = new Cert
            {
                IssuerSerial = new IssuerSerial
                {
                    X509IssuerName = bouncyCert.IssuerDN.ToX509IssuerName(),
                    X509SerialNumber = bouncyCert.SerialNumber.ToString()
                },
                CertDigest =
                {
                    DigestValue = hashValue,
                    DigestMethod = new DigestMethod { Algorithm = DigestMethod }
                }
            };

            var signedSignatureProperties = xadesObject.QualifyingProperties.SignedProperties.SignedSignatureProperties;
            signedSignatureProperties.SigningCertificate.CertCollection.Add(cert);
            signedSignatureProperties.SigningTime = xadesInfo.SigningDateTimeUtc.ToDateTimeOffset(xadesInfo.TimeZoneOffsetMinutes);

            return xadesObject;
        }
    }
}
