using System.Collections.Concurrent;
using System.Security.Cryptography;
using ShrinkNetwork;

namespace ShrinkNetwork.ServerHost.Framework;

public sealed class ServerAuthStore
{
    public sealed class AuthState
    {
        public string AuthName { get; set; } = string.Empty;
        public bool IsAuthenticated { get; set; }
        public DateTimeOffset AuthenticatedAtUtc { get; set; }
        public DateTimeOffset LastSeenAtUtc { get; set; }
        public string SessionToken { get; set; } = string.Empty;
        public DateTimeOffset SessionTokenExpiresAtUtc { get; set; }
    }

    private readonly ConcurrentDictionary<(ShrinkNetworkService service, long sessionId), AuthState> _states = new();

    public AuthState MarkAuthenticated(ShrinkNetworkService service, long sessionId, string authName, TimeSpan tokenTtl)
    {
        var now = DateTimeOffset.UtcNow;
        var state = new AuthState
        {
            AuthName = authName,
            IsAuthenticated = true,
            AuthenticatedAtUtc = now,
            LastSeenAtUtc = now,
            SessionToken = CreateSessionToken(),
            SessionTokenExpiresAtUtc = now.Add(tokenTtl)
        };
        _states[(service, sessionId)] = state;
        return CloneState(state);
    }

    public void Remove(ShrinkNetworkService service, long sessionId)
    {
        _states.TryRemove((service, sessionId), out _);
    }

    public bool IsAuthenticated(ShrinkNetworkService service, long sessionId)
    {
        return _states.TryGetValue((service, sessionId), out var state) && state.IsAuthenticated;
    }

    public bool TryGetState(ShrinkNetworkService service, long sessionId, out AuthState state)
    {
        if (_states.TryGetValue((service, sessionId), out var current))
        {
            state = CloneState(current);
            return true;
        }

        state = null!;
        return false;
    }

    public bool TryValidateSessionToken(ShrinkNetworkService service, long sessionId, string? sessionToken, out AuthState state,
        out string failureReason)
    {
        if (!_states.TryGetValue((service, sessionId), out var current) || !current.IsAuthenticated)
        {
            state = null!;
            failureReason = "会话未认证。";
            return false;
        }

        if (string.IsNullOrWhiteSpace(sessionToken))
        {
            state = null!;
            failureReason = "缺少会话令牌。";
            return false;
        }

        if (!string.Equals(current.SessionToken, sessionToken.Trim(), StringComparison.Ordinal))
        {
            state = null!;
            failureReason = "会话令牌不匹配。";
            return false;
        }

        var now = DateTimeOffset.UtcNow;
        if (current.SessionTokenExpiresAtUtc <= now)
        {
            state = null!;
            failureReason = "会话令牌已过期。";
            return false;
        }

        current.LastSeenAtUtc = now;
        state = CloneState(current);
        failureReason = string.Empty;
        return true;
    }

    public bool TryRefreshSessionToken(ShrinkNetworkService service, long sessionId, string? sessionToken, TimeSpan tokenTtl,
        TimeSpan refreshWindow, out AuthState state, out string failureReason)
    {
        if (!TryValidateSessionToken(service, sessionId, sessionToken, out _, out failureReason))
        {
            state = null!;
            return false;
        }

        var key = (service, sessionId);
        if (!_states.TryGetValue(key, out var current))
        {
            state = null!;
            failureReason = "认证状态不存在。";
            return false;
        }

        var now = DateTimeOffset.UtcNow;
        if (refreshWindow > TimeSpan.Zero && current.SessionTokenExpiresAtUtc - now > refreshWindow)
        {
            state = null!;
            failureReason = "当前还不在会话令牌续期窗口内。";
            return false;
        }

        current.SessionToken = CreateSessionToken();
        current.SessionTokenExpiresAtUtc = now.Add(tokenTtl);
        current.LastSeenAtUtc = now;
        state = CloneState(current);
        failureReason = string.Empty;
        return true;
    }

    private static AuthState CloneState(AuthState state)
    {
        return new AuthState
        {
            AuthName = state.AuthName,
            IsAuthenticated = state.IsAuthenticated,
            AuthenticatedAtUtc = state.AuthenticatedAtUtc,
            LastSeenAtUtc = state.LastSeenAtUtc,
            SessionToken = state.SessionToken,
            SessionTokenExpiresAtUtc = state.SessionTokenExpiresAtUtc
        };
    }

    private static string CreateSessionToken()
    {
        var buffer = new byte[32];
        RandomNumberGenerator.Fill(buffer);
        return Convert.ToBase64String(buffer)
            .TrimEnd('=')
            .Replace('+', '-')
            .Replace('/', '_');
    }
}
