using System;
using System.Security.Cryptography;
using System.Text;

namespace MajacloudSdk.Internal
{
    internal static class Base64Url
    {
        public static string Encode(byte[] bytes)
        {
            if (bytes == null) return string.Empty;
            return Convert.ToBase64String(bytes).TrimEnd('=').Replace('+', '-').Replace('/', '_');
        }

        public static byte[] Decode(string text)
        {
            if (string.IsNullOrEmpty(text)) return new byte[0];
            var padded = text.Replace('-', '+').Replace('_', '/');
            switch (padded.Length % 4)
            {
                case 2: padded += "=="; break;
                case 3: padded += "="; break;
                case 1: throw new FormatException("Invalid base64url length");
            }
            return Convert.FromBase64String(padded);
        }
    }

    internal static class MajacloudCrypto
    {
        public static byte[] Sha256(byte[] data)
        {
            using (var sha = SHA256.Create()) return sha.ComputeHash(data);
        }

        public static string Sha256Hex(string utf8Text)
        {
            var hash = Sha256(Encoding.UTF8.GetBytes(utf8Text ?? string.Empty));
            var sb = new StringBuilder(hash.Length * 2);
            foreach (var b in hash) sb.Append(b.ToString("x2"));
            return sb.ToString();
        }

        public static byte[] RandomBytes(int count)
        {
            var bytes = new byte[count];
            try
            {
                using (var rng = RandomNumberGenerator.Create()) rng.GetBytes(bytes);
            }
            catch (Exception)
            {
                // Some stripped runtimes lack a CSPRNG; fall back to Guid entropy.
                int offset = 0;
                while (offset < count)
                {
                    var chunk = Guid.NewGuid().ToByteArray();
                    int length = Math.Min(chunk.Length, count - offset);
                    Array.Copy(chunk, 0, bytes, offset, length);
                    offset += length;
                }
            }
            return bytes;
        }

        /// <summary>Parse &lt;RSAKeyValue&gt;&lt;Modulus/&gt;&lt;Exponent/&gt;&lt;/RSAKeyValue&gt; without System.Xml.</summary>
        public static RSAParameters ParseRsaPublicKeyXml(string xml)
        {
            if (string.IsNullOrEmpty(xml)) throw new FormatException("Empty RSA public key");
            return new RSAParameters
            {
                Modulus = TrimLeadingZero(Convert.FromBase64String(Extract(xml, "Modulus"))),
                Exponent = TrimLeadingZero(Convert.FromBase64String(Extract(xml, "Exponent"))),
            };
        }

        private static string Extract(string xml, string tag)
        {
            int start = xml.IndexOf("<" + tag + ">", StringComparison.OrdinalIgnoreCase);
            int end = xml.IndexOf("</" + tag + ">", StringComparison.OrdinalIgnoreCase);
            if (start < 0 || end < 0 || end <= start) throw new FormatException("RSA public key is missing <" + tag + ">");
            start += tag.Length + 2;
            return xml.Substring(start, end - start).Trim();
        }

        private static byte[] TrimLeadingZero(byte[] bytes)
        {
            if (bytes.Length > 1 && bytes[0] == 0)
            {
                var trimmed = new byte[bytes.Length - 1];
                Array.Copy(bytes, 1, trimmed, 0, trimmed.Length);
                return trimmed;
            }
            return bytes;
        }

        /// <summary>RSA-SHA256 PKCS#1 v1.5 verification (matches PHP openssl_sign with OPENSSL_ALGO_SHA256).</summary>
        public static bool VerifyRsaSha256Pkcs1(byte[] data, byte[] signature, string publicKeyXml, out string error)
        {
            error = null;
            RSAParameters parameters;
            try
            {
                parameters = ParseRsaPublicKeyXml(publicKeyXml);
            }
            catch (Exception e)
            {
                error = "Invalid public key: " + e.Message;
                return false;
            }
            try
            {
                using (var rsa = RSA.Create())
                {
                    rsa.ImportParameters(parameters);
                    return rsa.VerifyData(data, signature, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
                }
            }
            catch (Exception first)
            {
                try
                {
                    using (var legacy = new RSACryptoServiceProvider())
                    {
                        legacy.ImportParameters(parameters);
                        return legacy.VerifyData(data, "SHA256", signature);
                    }
                }
                catch (Exception second)
                {
                    error = "RSA verification unavailable: " + first.Message + " / " + second.Message;
                    return false;
                }
            }
        }
    }

    /// <summary>PKCE helpers (RFC 7636, S256).</summary>
    internal static class Pkce
    {
        public static string CreateVerifier() => Base64Url.Encode(MajacloudCrypto.RandomBytes(32));

        public static string CreateChallenge(string verifier) => Base64Url.Encode(MajacloudCrypto.Sha256(Encoding.ASCII.GetBytes(verifier)));
    }
}
