using System.Text.Json;

namespace Tiku.Application.Security;

public enum DataScopeMode
{
    Self,
    Restricted,
    All
}

public sealed record CurrentDataScope(
    DataScopeMode Mode,
    IReadOnlySet<Guid> RegionIds,
    IReadOnlySet<Guid> ClassIds,
    bool IncludesSelf)
{
    public static CurrentDataScope Self { get; } = new(
        DataScopeMode.Self,
        new HashSet<Guid>(),
        new HashSet<Guid>(),
        true);

    public bool AllowsResource(Guid currentUserId, Guid? ownerUserId = null, Guid? regionId = null, Guid? classId = null)
    {
        if (Mode == DataScopeMode.All)
        {
            return true;
        }

        if (IncludesSelf && ownerUserId == currentUserId)
        {
            return true;
        }

        return Mode == DataScopeMode.Restricted &&
               ((regionId.HasValue && RegionIds.Contains(regionId.Value)) ||
                (classId.HasValue && ClassIds.Contains(classId.Value)));
    }

    public static CurrentDataScope Merge(IEnumerable<JsonElement> roleScopes)
    {
        var regionIds = new HashSet<Guid>();
        var classIds = new HashSet<Guid>();
        var includesSelf = false;
        var hasRestrictedScope = false;

        foreach (var roleScope in roleScopes)
        {
            var parsed = Parse(roleScope);
            if (parsed.Mode == DataScopeMode.All)
            {
                return new CurrentDataScope(DataScopeMode.All, new HashSet<Guid>(), new HashSet<Guid>(), true);
            }

            includesSelf |= parsed.IncludesSelf;
            hasRestrictedScope |= parsed.Mode == DataScopeMode.Restricted;
            regionIds.UnionWith(parsed.RegionIds);
            classIds.UnionWith(parsed.ClassIds);
        }

        return hasRestrictedScope || regionIds.Count > 0 || classIds.Count > 0
            ? new CurrentDataScope(DataScopeMode.Restricted, regionIds, classIds, includesSelf)
            : Self;
    }

    private static CurrentDataScope Parse(JsonElement value)
    {
        if (value.ValueKind != JsonValueKind.Object)
        {
            return Self;
        }

        var mode = ReadString(value, "mode") ?? ReadString(value, "type");
        if (string.Equals(mode, nameof(DataScopeMode.All), StringComparison.OrdinalIgnoreCase))
        {
            return new CurrentDataScope(DataScopeMode.All, new HashSet<Guid>(), new HashSet<Guid>(), true);
        }

        if (string.Equals(mode, nameof(DataScopeMode.Self), StringComparison.OrdinalIgnoreCase))
        {
            return Self;
        }

        var regions = ReadGuids(value, "regionIds");
        var classes = ReadGuids(value, "classIds");
        var restricted = string.Equals(mode, nameof(DataScopeMode.Restricted), StringComparison.OrdinalIgnoreCase) ||
                         regions.Count > 0 ||
                         classes.Count > 0;

        return restricted
            ? new CurrentDataScope(DataScopeMode.Restricted, regions, classes, ReadBoolean(value, "includesSelf") || ReadBoolean(value, "ownLeadsOnly"))
            : Self;
    }

    private static string? ReadString(JsonElement value, string propertyName)
    {
        return value.TryGetProperty(propertyName, out var property) && property.ValueKind == JsonValueKind.String
            ? property.GetString()
            : null;
    }

    private static bool ReadBoolean(JsonElement value, string propertyName)
    {
        return value.TryGetProperty(propertyName, out var property) &&
               property.ValueKind is JsonValueKind.True or JsonValueKind.False &&
               property.GetBoolean();
    }

    private static HashSet<Guid> ReadGuids(JsonElement value, string propertyName)
    {
        var result = new HashSet<Guid>();
        if (!value.TryGetProperty(propertyName, out var property) || property.ValueKind != JsonValueKind.Array)
        {
            return result;
        }

        foreach (var item in property.EnumerateArray())
        {
            if (item.ValueKind == JsonValueKind.String && Guid.TryParse(item.GetString(), out var id))
            {
                result.Add(id);
            }
        }

        return result;
    }
}

public sealed record CurrentAccessSnapshot(
    Guid? UserId,
    Guid? TenantId,
    bool IsUserActive,
    bool IsCurrentTenantMember,
    IReadOnlySet<string> TenantPermissions,
    IReadOnlySet<string> PlatformPermissions,
    CurrentDataScope DataScope)
{
    public bool HasTenantPermission(string permissionCode) =>
        IsCurrentTenantMember && TenantPermissions.Contains(permissionCode);

    public bool HasPlatformPermission(string permissionCode) =>
        IsUserActive && PlatformPermissions.Contains(permissionCode);
}

public interface ICurrentAccessContext
{
    Task<CurrentAccessSnapshot> GetAsync(CancellationToken cancellationToken = default);
}
