using CryptoPro.Security.Cryptography;
using CryptoPro.Security.Cryptography.X509Certificates;
using CryptoPro.Security.Cryptography.Xml;
using System.Globalization;
using System.Numerics;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using System.Xml;

namespace Hcs.Broker.Api.Request
{
    internal class SignedXml(XmlDocument document) : CpXadesSignedXml(document)
    {
        private const string XADES_141_NAMESPACE_URL = "http://uri.etsi.org/01903/v1.4.1#";

        /// <summary>
        /// OID алгоритма открытого ключа/цифровой подписи ГОСТ Р 34.10-2001
        /// </summary>
        private const string OID_CP_GOST_R3410EL = "1.2.643.2.2.19";

        /// <summary>
        /// OID алгоритма открытого ключа/цифровой подписи ГОСТ Р 34.10-2012 256
        /// </summary>
        private const string OID_CP_GOST_R3410_12_256 = "1.2.643.7.1.1.1.1";

        /// <summary>
        /// OID алгоритма открытого ключа/цифровой подписи ГОСТ Р 34.10-2012 512
        /// </summary>
        private const string OID_CP_GOST_R3410_12_512 = "1.2.643.7.1.1.1.2";

        protected override void AddXadesProperties(
            CpX509Certificate2 signingCertificate,
            DateTime time,
            string signatureId,
            string objectId,
            string? xmlDSigPrefix,
            XadesPropertiesFlags xadesProperties)
        {
            if (_containingDocument == null)
            {
                throw new InvalidOperationException("XmlDocument is null");
            }

            var useCertificateV2 = xadesProperties.HasFlag(
                XadesPropertiesFlags.UseSigningCertificateV2
            );
            var useCertificateV1 = xadesProperties.HasFlag(
                XadesPropertiesFlags.UseSigningCertificateV1
            );
            if ((useCertificateV2 && useCertificateV1) || (!useCertificateV2 && !useCertificateV1))
            {
                throw new InvalidOperationException("Invalid flags");
            }

            var document = _containingDocument;

            var parametersSignature = new CpReference
            {
                Uri = $"#{objectId}",
                Type = XmlDsigSignatureProperties,
                DigestMethod = XmlDsigGost3411_2012_256Url,
            };
            parametersSignature.AddTransform(new CpXmlDsigExcC14NTransform());

            AddReference(parametersSignature);

            var qualifyingPropertiesNode = document.CreateElement(
                XadesPrefix,
                "QualifyingProperties",
                XadesNamespaceUrl
            );
            qualifyingPropertiesNode.SetAttribute("xmlns:xades141", XADES_141_NAMESPACE_URL);
            qualifyingPropertiesNode.SetAttribute("Target", $"#{signatureId}");

            var signedPropertiesNode = document.CreateElement(
                XadesPrefix,
                "SignedProperties",
                XadesNamespaceUrl
            );
            signedPropertiesNode.SetAttribute("Id", objectId);

            qualifyingPropertiesNode.AppendChild(signedPropertiesNode);

            var signedSignaturePropertiesNode = document.CreateElement(
                XadesPrefix,
                "SignedSignatureProperties",
                XadesNamespaceUrl
            );
            signedPropertiesNode.AppendChild(signedSignaturePropertiesNode);

            var signingTime = document.CreateElement(XadesPrefix, "SigningTime", XadesNamespaceUrl);
            signingTime.InnerText = time.ToString("yyyy-MM-ddTHH:mm:ss.fffZ");

            signedSignaturePropertiesNode.AppendChild(signingTime);

            var signingCertificateNodeName = useCertificateV2
                ? "SigningCertificateV2"
                : "SigningCertificate";
            var signingCertificateNode = document.CreateElement(
                XadesPrefix,
                signingCertificateNodeName,
                XadesNamespaceUrl
            );

            signedSignaturePropertiesNode.AppendChild(signingCertificateNode);

            var certNode = document.CreateElement(XadesPrefix, "Cert", XadesNamespaceUrl);
            signingCertificateNode.AppendChild(certNode);

            var certDigestNode = document.CreateElement(
                XadesPrefix,
                "CertDigest",
                XadesNamespaceUrl
            );
            certNode.AppendChild(certDigestNode);

            var algIdString = signingCertificate.GetKeyAlgorithm();
            (bool isGost, string? gostDigestAlgorithm) = algIdString switch
            {
                OID_CP_GOST_R3410EL => (true, XmlDsigGost3411Url),
                OID_CP_GOST_R3410_12_256 => (
                    true,
                    XmlDsigGost3411_2012_256Url
                ),
                OID_CP_GOST_R3410_12_512 => (
                    true,
                    XmlDsigGost3411_2012_512Url
                ),
                _ => (false, null),
            };

            var digestMethodAlgorithmAtribute = document.CreateAttribute("Algorithm");
            if (isGost)
            {
                digestMethodAlgorithmAtribute.InnerText = gostDigestAlgorithm!;
            }
            else
            {
                digestMethodAlgorithmAtribute.InnerText = XmlDsigSHA1Url;
            }

            var digestMethod = document.CreateElement(
                xmlDSigPrefix,
                "DigestMethod",
                XmlDsigNamespaceUrl
            );
            digestMethod.Attributes.Append(digestMethodAlgorithmAtribute);

            certDigestNode.AppendChild(digestMethod);

            var digestValue = document.CreateElement(
                xmlDSigPrefix,
                "DigestValue",
                XmlDsigNamespaceUrl
            );

            if (isGost)
            {
                var hash =
                    CpCryptoConfig.CreateFromName(gostDigestAlgorithm!) as HashAlgorithm
                    ?? throw new System.Exception(
                        $"Cannot find [{gostDigestAlgorithm}] alg in CpCryptoConfig!"
                    );
                using (hash)
                {
                    digestValue.InnerText = Convert.ToBase64String(
                        hash.ComputeHash(signingCertificate.RawData)
                    );
                }
            }
            else
            {
                digestValue.InnerText = Convert.ToBase64String(signingCertificate.GetCertHash());
            }

            certDigestNode.AppendChild(digestValue);

            if (useCertificateV2)
            {
                var issuerSerialNode = document.CreateElement(
                    XadesPrefix,
                    "IssuerSerialV2",
                    XadesNamespaceUrl
                );
                issuerSerialNode.InnerText = Convert.ToBase64String(
                    X509EncodeIssuerAndSerialHelper.EncodeIssuerAndSerial(signingCertificate)
                );

                certNode.AppendChild(issuerSerialNode);
            }
            else
            {
                var issuerName = signingCertificate.IssuerName.Decode(X500DistinguishedNameFlags.DoNotUseQuotes);
                var x509IssuerNameNode = document.CreateElement(
                    xmlDSigPrefix,
                    "X509IssuerName",
                    XmlDsigNamespaceUrl
                );
                x509IssuerNameNode.InnerText = GetOidRepresentation(issuerName);

                var issuerSerialNode = document.CreateElement(
                    XadesPrefix,
                    "IssuerSerial",
                    XadesNamespaceUrl
                );
                issuerSerialNode.AppendChild(x509IssuerNameNode);

                var x509SerialNumber = document.CreateElement(
                    xmlDSigPrefix,
                    "X509SerialNumber",
                    XmlDsigNamespaceUrl
                );
                x509SerialNumber.InnerText = BigInteger.Parse(signingCertificate.SerialNumber, NumberStyles.HexNumber).ToString();
                
                issuerSerialNode.AppendChild(x509SerialNumber);
                certNode.AppendChild(issuerSerialNode);
            }

            var dataObject = new CpDataObject();
            dataObject.Data = qualifyingPropertiesNode.SelectNodes(".");

            AddObject(dataObject);
        }

        private string GetOidRepresentation(string issuerName)
        {
            return issuerName
                .Replace("\"", "\\\"")
                .Replace("E=", "1.2.840.113549.1.9.1=")
                .Replace("unstructuredName=", "1.2.840.113549.1.9.2=")
                .Replace("ОГРН=", "1.2.643.100.1=")
                .Replace("ИНН ЮЛ=", "1.2.643.100.4=")
                .Replace("ИНН ФЛ=", "1.2.643.3.131.1.1=")
                .Replace("ИНН=", "1.2.643.3.131.1.1=")
                .Replace("СНИЛС=", "1.2.643.100.3=")
                .Replace("ОГРНИП=", "1.2.643.100.5=")
                .Replace("S=", "ST=");
        }
    }
}
