using System.Text.Json;
using Microsoft.EntityFrameworkCore;
using NodaTime;

namespace DysonNetwork.Sphere.Permission;

public class PermissionService(AppDatabase db)
{
    public async Task<bool> HasPermissionAsync(string actor, string area, string key)
    {
        var value = await GetPermissionAsync<bool>(actor, area, key);
        return value;
    }

    public async Task<T?> GetPermissionAsync<T>(string actor, string area, string key)
    {
        var now = SystemClock.Instance.GetCurrentInstant();
        var groupsId = await db.PermissionGroupMembers
            .Where(n => n.Actor == actor)
            .Where(n => n.ExpiredAt == null || n.ExpiredAt < now)
            .Where(n => n.AffectedAt == null || n.AffectedAt >= now)
            .Select(e => e.GroupId)
            .ToListAsync();
        var permission = await db.PermissionNodes
            .Where(n => n.GroupId == null || groupsId.Contains(n.GroupId.Value))
            .Where(n => (n.Key == key || n.Key == "*") && (n.GroupId != null || n.Actor == actor) && n.Area == area)
            .Where(n => n.ExpiredAt == null || n.ExpiredAt < now)
            .Where(n => n.AffectedAt == null || n.AffectedAt >= now)
            .FirstOrDefaultAsync();

        return permission is not null ? _DeserializePermissionValue<T>(permission.Value) : default;
    }

    public async Task<PermissionNode> AddPermissionNode<T>(
        string actor,
        string area,
        string key,
        T value,
        Instant? expiredAt = null,
        Instant? affectedAt = null
    )
    {
        if (value is null) throw new ArgumentNullException(nameof(value));

        var node = new PermissionNode
        {
            Actor = actor,
            Key = key,
            Area = area,
            Value = _SerializePermissionValue(value),
            ExpiredAt = expiredAt,
            AffectedAt = affectedAt
        };

        db.PermissionNodes.Add(node);
        await db.SaveChangesAsync();

        return node;
    }

    public async Task<PermissionNode> AddPermissionNodeToGroup<T>(
        PermissionGroup group,
        string actor,
        string area,
        string key,
        T value,
        Instant? expiredAt = null,
        Instant? affectedAt = null
    )
    {
        if (value is null) throw new ArgumentNullException(nameof(value));

        var node = new PermissionNode
        {
            Actor = actor,
            Key = key,
            Area = area,
            Value = _SerializePermissionValue(value),
            ExpiredAt = expiredAt,
            AffectedAt = affectedAt,
            Group = group,
            GroupId = group.Id
        };

        db.PermissionNodes.Add(node);
        await db.SaveChangesAsync();

        return node;
    }

    public async Task RemovePermissionNode(string actor, string area, string key)
    {
        var node = await db.PermissionNodes
            .Where(n => n.Actor == actor && n.Area == area && n.Key == key)
            .FirstOrDefaultAsync();
        if (node is not null) db.PermissionNodes.Remove(node);
        await db.SaveChangesAsync();
    }

    public async Task RemovePermissionNodeFromGroup<T>(PermissionGroup group, string actor, string area, string key)
    {
        var node = await db.PermissionNodes
            .Where(n => n.GroupId == group.Id)
            .Where(n => n.Actor == actor && n.Area == area && n.Key == key)
            .FirstOrDefaultAsync();
        if (node is null) return;
        db.PermissionNodes.Remove(node);
        await db.SaveChangesAsync();
    }

    private static T? _DeserializePermissionValue<T>(JsonDocument json)
    {
        return JsonSerializer.Deserialize<T>(json.RootElement.GetRawText());
    }

    private static JsonDocument _SerializePermissionValue<T>(T obj)
    {
        var str = JsonSerializer.Serialize(obj);
        return JsonDocument.Parse(str);
    }

    public static PermissionNode NewPermissionNode<T>(string actor, string area, string key, T value)
    {
        return new PermissionNode
        {
            Actor = actor,
            Area = area,
            Key = key,
            Value = _SerializePermissionValue(value),
        };
    }
}