using System;
using System.Collections.Generic;
using System.IO;
using System.Text.Json;
using System.Threading.Tasks;

namespace Dacryptero.DB
{
    public class DBConfig
    {
        public string Name { get; }

        public string DirectoryPath { get; }

        public ReadOnlyMemory<byte> RecoveryKeyHash { get; }

        public string RoleSchema { get; }

        public List<DBAccess> Access { get; } = new List<DBAccess>();

        public bool ContainsIndex { get; set; }

        public DBConfig(string name, string directoryPath, ReadOnlyMemory<byte> encRecoveryKey, 
            string roleSchema)
        {
            Name = name;
            DirectoryPath = directoryPath;
            RecoveryKeyHash = encRecoveryKey;
            RoleSchema = roleSchema;
        }

        public static async Task<DBConfig?> TryParseAsync(string configPath)
        {
            if (!File.Exists(configPath))
                return null;
            // parse json
            var data = await File.ReadAllBytesAsync(configPath).ConfigureAwait(false);
            JsonDocument document;
            try
            {
                document = JsonDocument.Parse(data, new JsonDocumentOptions
                {
                    AllowTrailingCommas = true,
                    CommentHandling = JsonCommentHandling.Skip,
                });
            }
            catch (JsonException e)
            {
                Serilog.Log.Error(e, "Cannot parse db config at {path}", configPath);
                return null;
            }
            // name
            if (!document.RootElement.TryGetProperty("name", out JsonElement node))
                return null;
            var name = node.GetString();
            if (name is null)
                return null;
            // dir name
            var dirName = new FileInfo(configPath).DirectoryName;
            if (dirName is null)
                return null;
            // enc recovery key
            if (!document.RootElement.TryGetProperty("recovery-key-hash", out node)
                || !node.TryGetBytesFromBase64(out byte[]? encRecoveryKey))
                return null;
            // role schema
            if (!document.RootElement.TryGetProperty("role-schema", out node))
                return null;
            var roleSchema = node.GetString();
            if (roleSchema is null)
                return null;
            // pre finish
            var config = new DBConfig(name, dirName, encRecoveryKey, roleSchema);
            // get access
            if (!document.RootElement.TryGetProperty("access", out node) 
                || node.ValueKind != JsonValueKind.Array)
                return null;
            foreach (var item in node.EnumerateArray())
                if (DBAccess.TryParse(item, out DBAccess? access))
                    config.Access.Add(access);
                else return null;
            // get index
            if (document.RootElement.TryGetProperty("contains-index", out node))
                config.ContainsIndex = node.GetBoolean();
            // finish
            return config;
        }

        public async Task SaveAsync()
        {
            // create directory if doesn't exists
            if (!Directory.Exists(DirectoryPath))
                Directory.CreateDirectory(DirectoryPath);
            // backup config
            var path = Path.Combine(DirectoryPath, "config.json");
            if (File.Exists(path))
                File.Copy(path, $"{path}.backup", true);
            // save config
            using var file = new FileStream(
                path,
                FileMode.OpenOrCreate,
                FileAccess.Write,
                FileShare.Read
            );
            using var writer = new Utf8JsonWriter(file, new JsonWriterOptions
            {
                Indented = true,
            });
            writer.WriteStartObject();
            writer.WriteString("name", Name);
            writer.WriteBase64String("recovery-key-hash", RecoveryKeyHash.Span);
            writer.WriteString("role-schema", RoleSchema);
            writer.WriteStartArray("access");
            foreach (var access in Access)
                access.WriteTo(writer);
            writer.WriteEndArray();
            writer.WriteBoolean("contains-index", ContainsIndex);
            writer.WriteEndObject();
            await writer.FlushAsync().ConfigureAwait(false);
            file.SetLength(file.Position);
        }
    
#if DEBUG
        public static DBConfig GetTestConfig(string rootDir, string? prefix = null,
            string schema = "/internal/schemas/large-test.json"
        )
        {
            prefix = prefix is null ? "" : $"{prefix}-";
            return new DBConfig(
                $"{prefix}test",
                Path.Combine(rootDir, $"{prefix}test"),
                new byte[0],
                schema
            );
        }
#endif
    }
}