using System.Text;
using System.Text.Json;
using System.Security.Cryptography;
using GestionaDenuncias.Shared.Models;
using Microsoft.AspNetCore.DataProtection;

namespace ApiDenuncias.Services;

public sealed class GlobalLeaksSessionStore
{
    private const string RootPath = @"C:\GestionaDenuncias\.gl-auth";
    private static readonly JsonSerializerOptions JsonOptions = new(JsonSerializerDefaults.Web)
    {
        WriteIndented = false,
    };

    private readonly SemaphoreSlim _gate = new(1, 1);
    private readonly IDataProtector _protector;

    public GlobalLeaksSessionStore(IDataProtectionProvider dataProtectionProvider)
    {
        _protector = dataProtectionProvider.CreateProtector("GestionaDenunciasAN.GlobalLeaksSessionStore");
    }

    public async Task<GlobalLeaksStoredSession?> GetAsync(string username, CancellationToken cancellationToken = default)
    {
        if (string.IsNullOrWhiteSpace(username))
        {
            return null;
        }

        var path = GetFilePath(username);
        await _gate.WaitAsync(cancellationToken);
        try
        {
            return await ReadUnsafeAsync(path, cancellationToken);
        }
        finally
        {
            _gate.Release();
        }
    }

    public async Task SaveAsync(
        string username,
        string password,
        string sessionId,
        string? role,
        string? dpopPrivateKey,
        GlobalLeaksProofOfWorkToken? proofOfWorkToken,
        DateTimeOffset? sessionExpiresAtUtc,
        CancellationToken cancellationToken = default)
    {
        var now = DateTimeOffset.UtcNow;
        var data = new GlobalLeaksStoredSession
        {
            Username = username,
            Password = password,
            SessionId = sessionId,
            Role = role,
            DpopPrivateKey = dpopPrivateKey,
            ProofOfWorkToken = proofOfWorkToken,
            SessionExpiresAtUtc = sessionExpiresAtUtc,
            LastKeepAliveAtUtc = now,
            UpdatedAt = now,
        };

        await WriteAsync(data, cancellationToken);
    }

    public async Task UpdateSessionAsync(
        string username,
        string sessionId,
        string? role,
        string? dpopPrivateKey,
        GlobalLeaksProofOfWorkToken? proofOfWorkToken,
        DateTimeOffset? sessionExpiresAtUtc,
        CancellationToken cancellationToken = default)
    {
        var path = GetFilePath(username);

        await _gate.WaitAsync(cancellationToken);
        try
        {
            var current = await ReadUnsafeAsync(path, cancellationToken)
                ?? throw new InvalidOperationException("No hay credenciales guardadas para este usuario.");
            var now = DateTimeOffset.UtcNow;

            current.SessionId = sessionId;
            current.Role = role;
            current.DpopPrivateKey = dpopPrivateKey;
            current.ProofOfWorkToken = proofOfWorkToken;
            current.SessionExpiresAtUtc = sessionExpiresAtUtc;
            current.LastKeepAliveAtUtc = now;
            current.UpdatedAt = now;

            await WriteUnsafeAsync(path, current, cancellationToken);
        }
        finally
        {
            _gate.Release();
        }
    }

    public async Task<bool> UpdateKeepAliveAsync(
        string username,
        string expectedSessionId,
        GlobalLeaksProofOfWorkToken proofOfWorkToken,
        DateTimeOffset? sessionExpiresAtUtc,
        CancellationToken cancellationToken = default)
    {
        var path = GetFilePath(username);

        await _gate.WaitAsync(cancellationToken);
        try
        {
            var current = await ReadUnsafeAsync(path, cancellationToken);
            if (current is null ||
                !string.Equals(current.SessionId, expectedSessionId, StringComparison.Ordinal))
            {
                return false;
            }

            var now = DateTimeOffset.UtcNow;
            current.ProofOfWorkToken = proofOfWorkToken;
            current.SessionExpiresAtUtc = sessionExpiresAtUtc;
            current.LastKeepAliveAtUtc = now;
            current.UpdatedAt = now;
            await WriteUnsafeAsync(path, current, cancellationToken);
            return true;
        }
        finally
        {
            _gate.Release();
        }
    }

    public async Task ClearSessionAsync(string username, CancellationToken cancellationToken = default)
    {
        var path = GetFilePath(username);

        await _gate.WaitAsync(cancellationToken);
        try
        {
            var current = await ReadUnsafeAsync(path, cancellationToken);
            if (current is null)
            {
                return;
            }

            ClearSessionValues(current);
            await WriteUnsafeAsync(path, current, cancellationToken);
        }
        finally
        {
            _gate.Release();
        }
    }

    public async Task<bool> ClearSessionIfMatchesAsync(
        string username,
        string expectedSessionId,
        CancellationToken cancellationToken = default)
    {
        var path = GetFilePath(username);

        await _gate.WaitAsync(cancellationToken);
        try
        {
            var current = await ReadUnsafeAsync(path, cancellationToken);
            if (current is null ||
                !string.Equals(current.SessionId, expectedSessionId, StringComparison.Ordinal))
            {
                return false;
            }

            ClearSessionValues(current);
            await WriteUnsafeAsync(path, current, cancellationToken);
            return true;
        }
        finally
        {
            _gate.Release();
        }
    }

    public async Task DeleteAsync(string username, CancellationToken cancellationToken = default)
    {
        if (string.IsNullOrWhiteSpace(username))
        {
            return;
        }

        var path = GetFilePath(username);

        await _gate.WaitAsync(cancellationToken);
        try
        {
            if (File.Exists(path))
            {
                File.Delete(path);
            }
        }
        finally
        {
            _gate.Release();
        }
    }

    private async Task WriteAsync(GlobalLeaksStoredSession data, CancellationToken cancellationToken)
    {
        var path = GetFilePath(data.Username);

        await _gate.WaitAsync(cancellationToken);
        try
        {
            await WriteUnsafeAsync(path, data, cancellationToken);
        }
        finally
        {
            _gate.Release();
        }
    }

    private static string GetFilePath(string username)
    {
        Directory.CreateDirectory(RootPath);
        var normalized = username.Trim().ToLowerInvariant();
        var hash = SHA256.HashData(Encoding.UTF8.GetBytes(normalized));
        return Path.Combine(RootPath, $"{Convert.ToHexString(hash).ToLowerInvariant()}.bin");
    }

    private async Task<GlobalLeaksStoredSession?> ReadUnsafeAsync(
        string path,
        CancellationToken cancellationToken)
    {
        if (!File.Exists(path))
        {
            return null;
        }

        var protectedBytes = await File.ReadAllBytesAsync(path, cancellationToken);
        var protectedBase64 = Encoding.UTF8.GetString(protectedBytes);
        var json = _protector.Unprotect(protectedBase64);
        return JsonSerializer.Deserialize<GlobalLeaksStoredSession>(json, JsonOptions);
    }

    private async Task WriteUnsafeAsync(
        string path,
        GlobalLeaksStoredSession data,
        CancellationToken cancellationToken)
    {
        Directory.CreateDirectory(RootPath);
        var json = JsonSerializer.Serialize(data, JsonOptions);
        var protectedValue = _protector.Protect(json);
        var protectedBytes = Encoding.UTF8.GetBytes(protectedValue);
        await File.WriteAllBytesAsync(path, protectedBytes, cancellationToken);
    }

    private static void ClearSessionValues(GlobalLeaksStoredSession session)
    {
        session.SessionId = null;
        session.DpopPrivateKey = null;
        session.ProofOfWorkToken = null;
        session.SessionExpiresAtUtc = null;
        session.LastKeepAliveAtUtc = null;
        session.UpdatedAt = DateTimeOffset.UtcNow;
    }
}
