using System;
using System.Collections.Generic;
using System.Text;

namespace MajacloudSdk.Internal
{
    internal sealed class ConfigSnapshot
    {
        public string ProjectId;
        public long Version;
        public string Checksum;
        public bool SignatureVerified;
        public Dictionary<string, MajacloudConfigValue> Values = new Dictionary<string, MajacloudConfigValue>();
        public List<string> Keys = new List<string>();
    }

    /// <summary>
    /// Pure verification of a Remote Config envelope: checksum, RSA signature
    /// over the raw `data` string, project match and version consistency.
    /// </summary>
    internal static class ConfigVerifier
    {
        public static bool TryParse(string envelopeJson, string projectId, string publicKeyXml, bool allowUnsigned, out ConfigSnapshot snapshot, out string error, out MajacloudErrorCode code)
        {
            snapshot = null;
            error = null;
            code = MajacloudErrorCode.ParseError;
            if (string.IsNullOrEmpty(envelopeJson))
            {
                error = "Config response is empty.";
                return false;
            }
            if (!JsonNode.TryParse(envelopeJson, out var envelope, out var parseError) || !envelope.IsObject)
            {
                error = "Config response is not valid JSON" + (parseError != null ? ": " + parseError : ".");
                return false;
            }
            var data = envelope.GetString("data");
            if (string.IsNullOrEmpty(data))
            {
                error = "Config response has no data.";
                return false;
            }
            var checksum = envelope.GetString("checksum") ?? string.Empty;
            if (!string.Equals(MajacloudCrypto.Sha256Hex(data), checksum, StringComparison.OrdinalIgnoreCase))
            {
                error = "Config checksum does not match.";
                code = MajacloudErrorCode.SignatureInvalid;
                return false;
            }

            var signature = envelope.GetString("signature");
            var verified = false;
            if (!string.IsNullOrEmpty(signature) && !string.IsNullOrEmpty(publicKeyXml))
            {
                byte[] signatureBytes;
                try
                {
                    signatureBytes = Convert.FromBase64String(signature);
                }
                catch (FormatException)
                {
                    error = "Config signature is malformed.";
                    code = MajacloudErrorCode.SignatureInvalid;
                    return false;
                }
                if (!MajacloudCrypto.VerifyRsaSha256Pkcs1(Encoding.UTF8.GetBytes(data), signatureBytes, publicKeyXml, out var cryptoError))
                {
                    error = "Config signature is invalid" + (cryptoError != null ? " (" + cryptoError + ")" : ".");
                    code = MajacloudErrorCode.SignatureInvalid;
                    return false;
                }
                verified = true;
            }
            else if (!allowUnsigned)
            {
                error = string.IsNullOrEmpty(publicKeyXml)
                    ? "No Remote Config public key is stored. Run Tools ▸ Majacloud ▸ Setup and test the connection."
                    : "Config response is not signed.";
                code = MajacloudErrorCode.SignatureInvalid;
                return false;
            }

            string payloadJson;
            try
            {
                payloadJson = Encoding.UTF8.GetString(Base64Url.Decode(data));
            }
            catch (Exception)
            {
                error = "Config data could not be decoded.";
                return false;
            }
            if (!JsonNode.TryParse(payloadJson, out var payload, out _) || !payload.IsObject)
            {
                error = "Config payload is not valid JSON.";
                return false;
            }
            if (payload.GetString("project_id") != projectId || envelope.GetString("project_id") != projectId)
            {
                error = "Config belongs to another project.";
                code = MajacloudErrorCode.SignatureInvalid;
                return false;
            }
            var version = payload.GetLong("version", -1);
            if (version < 0 || version != envelope.GetLong("version", -2))
            {
                error = "Config version is inconsistent.";
                code = MajacloudErrorCode.SignatureInvalid;
                return false;
            }

            var result = new ConfigSnapshot { ProjectId = projectId, Version = version, Checksum = checksum, SignatureVerified = verified };
            foreach (var item in payload.GetArray("configs").Elements())
            {
                var key = item.GetString("key");
                if (string.IsNullOrEmpty(key)) continue;
                var value = new MajacloudConfigValue { Key = key, Type = ParseType(item.GetString("type")), Raw = item.GetString("value") ?? string.Empty };
                result.Values[key] = value;
                result.Keys.Add(key);
            }
            snapshot = result;
            return true;
        }

        private static MajacloudConfigType ParseType(string type)
        {
            switch (type)
            {
                case "number": return MajacloudConfigType.Number;
                case "boolean": return MajacloudConfigType.Boolean;
                case "json": return MajacloudConfigType.Json;
                default: return MajacloudConfigType.String;
            }
        }
    }

    /// <summary>Persisted copy of the last verified envelope plus its ETag.</summary>
    internal sealed class ConfigCache
    {
        private readonly IMajacloudStorage _storage;
        private readonly string _projectId;
        private readonly int _maxBytes;

        public ConfigCache(IMajacloudStorage storage, string projectId, int maxBytes)
        {
            _storage = storage;
            _projectId = projectId;
            _maxBytes = maxBytes;
        }

        public bool TryLoad(out string envelopeJson, out string etag)
        {
            envelopeJson = _storage.GetString(StorageKeys.Config(_projectId));
            etag = _storage.GetString(StorageKeys.ConfigEtag(_projectId));
            return !string.IsNullOrEmpty(envelopeJson);
        }

        public bool Save(string envelopeJson, string etag)
        {
            if (string.IsNullOrEmpty(envelopeJson) || Encoding.UTF8.GetByteCount(envelopeJson) > _maxBytes) return false;
            _storage.SetString(StorageKeys.Config(_projectId), envelopeJson);
            if (!string.IsNullOrEmpty(etag)) _storage.SetString(StorageKeys.ConfigEtag(_projectId), etag);
            else _storage.Delete(StorageKeys.ConfigEtag(_projectId));
            _storage.Flush();
            return true;
        }

        public void Clear()
        {
            _storage.Delete(StorageKeys.Config(_projectId));
            _storage.Delete(StorageKeys.ConfigEtag(_projectId));
            _storage.Flush();
        }
    }
}
