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

namespace Dacryptero.Data.Schema
{
    public class SchemaFile
    {
        public string LocalPath { get; }

        public string Name { get; }

        public string? OnlineUrl { get; }

        public List<SchemaRole> Roles { get; }
            = new List<SchemaRole>();
        
        public IReadOnlyDictionary<string, SchemaRole> CombinedRoles { get; private set; }

        public SchemaFile(string localPath, string name, string? onlineUrl)
        {
            LocalPath = localPath;
            Name = name;
            OnlineUrl = onlineUrl;
            CombinedRoles = new Dictionary<string, SchemaRole>();
        }

        public SchemaFile(string localPath, JsonElement json)
        {
            LocalPath = localPath;
            Name = json.GetProperty("name").GetString() ?? "";
            OnlineUrl = json.TryGetProperty("online", out JsonElement node) ?
                node.GetString() : null;
            foreach (var role in json.GetProperty("roles").EnumerateArray())
                Roles.Add(new SchemaRole(role));
            CombinedRoles = new Dictionary<string, SchemaRole>();
        }

        public static async Task<SchemaFile?> TryLoadSchemaAsync(string path)
        {
            if (!System.IO.File.Exists(path))
                return null;
            try
            {
                using var file = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
                var doc = await JsonDocument.ParseAsync(file);
                var schema = new SchemaFile(path, doc.RootElement);
                schema.UpdateCombinedRoles();
                return schema;
            }
            catch (Exception e)
            {
                Serilog.Log.Warning(e, "Cannot load schema file {path}", path);
                return null;
            }
        }
    
        public async Task SaveConfig()
        {
            using var file = new FileStream(LocalPath, FileMode.OpenOrCreate, FileAccess.Write, FileShare.ReadWrite);
            using var writer = new Utf8JsonWriter(file, new JsonWriterOptions
            {
                Indented = true,
            });
            writer.WriteStartObject();
            writer.WriteString("name", Name);
            writer.WriteString("online", OnlineUrl);
            writer.WritePropertyName("roles");
            WriteRolesTo(writer);
            writer.WriteEndObject();
            await writer.FlushAsync().ConfigureAwait(false);
            file.SetLength(file.Position);
        }

        public void UpdateCombinedRoles()
        {
            var @base = new Dictionary<string, SchemaRole>();
            var result = new Dictionary<string, SchemaRole>();
            foreach (var role in Roles)
            {
                var @new = role.Clone();
                if (@new.Parent is not null &&
                    (@base.TryGetValue(@new.Parent, out SchemaRole? parent)
                        || result.TryGetValue(@new.Parent, out parent)
                    )
                )
                {
                    @new.Include(parent);
                }
                if (role.Abstract)
                    @base.Add(@new.Name, @new);
                else result.Add(@new.Name, @new);
            }
            CombinedRoles = result;
        }

        public void WriteRolesTo(Utf8JsonWriter writer)
        {
            writer.WriteStartArray();
            foreach (var role in Roles)
                role.WriteTo(writer);
            writer.WriteEndArray();
        }
    }
}