using System.Text.Json;
using System.Text.Json.Serialization;
using YMhut.Box.Core.App;
using YMhut.Box.Core.Logging;

namespace YMhut.Box.Core.Tools;

public interface IRiskConfirmationStore
{
    Task<bool> IsRememberedAsync(string toolId, string operation, CancellationToken cancellationToken = default);

    Task RememberAsync(string toolId, string operation, CancellationToken cancellationToken = default);

    Task ResetAsync(CancellationToken cancellationToken = default);
}

public sealed class RiskConfirmationStore(AppPaths paths, ILogService? logService = null) : IRiskConfirmationStore
{
    private readonly SemaphoreSlim _gate = new(1, 1);
    private readonly JsonSerializerOptions _jsonOptions = new(JsonSerializerDefaults.Web) { WriteIndented = true };

    private string StorePath => Path.Combine(paths.Data, "risk-confirmations.json");

    public async Task<bool> IsRememberedAsync(string toolId, string operation, CancellationToken cancellationToken = default)
    {
        await _gate.WaitAsync(cancellationToken).ConfigureAwait(false);
        try
        {
            var store = await LoadAsync(cancellationToken).ConfigureAwait(false);
            return store.Items.Any(item =>
                string.Equals(item.ToolId, toolId, StringComparison.OrdinalIgnoreCase) &&
                string.Equals(item.Operation, operation, StringComparison.OrdinalIgnoreCase));
        }
        finally
        {
            _gate.Release();
        }
    }

    public async Task RememberAsync(string toolId, string operation, CancellationToken cancellationToken = default)
    {
        if (string.IsNullOrWhiteSpace(toolId) || string.IsNullOrWhiteSpace(operation))
        {
            return;
        }

        await _gate.WaitAsync(cancellationToken).ConfigureAwait(false);
        try
        {
            var store = await LoadAsync(cancellationToken).ConfigureAwait(false);
            store.Items.RemoveAll(item =>
                string.Equals(item.ToolId, toolId, StringComparison.OrdinalIgnoreCase) &&
                string.Equals(item.Operation, operation, StringComparison.OrdinalIgnoreCase));
            store.Items.Add(new RiskConfirmationItem(toolId, operation, DateTimeOffset.Now));
            await SaveAsync(store, cancellationToken).ConfigureAwait(false);
            await WriteLogAsync("Information", "risk", "Risk confirmation remembered", $"{toolId} / {operation}", cancellationToken).ConfigureAwait(false);
        }
        finally
        {
            _gate.Release();
        }
    }

    public async Task ResetAsync(CancellationToken cancellationToken = default)
    {
        await _gate.WaitAsync(cancellationToken).ConfigureAwait(false);
        try
        {
            await SaveAsync(new RiskConfirmationFile(), cancellationToken).ConfigureAwait(false);
            await WriteLogAsync("Information", "risk", "Risk confirmations reset", null, cancellationToken).ConfigureAwait(false);
        }
        finally
        {
            _gate.Release();
        }
    }

    private async Task<RiskConfirmationFile> LoadAsync(CancellationToken cancellationToken)
    {
        try
        {
            if (!File.Exists(StorePath))
            {
                return new RiskConfirmationFile();
            }

            await using var stream = File.OpenRead(StorePath);
            return await JsonSerializer.DeserializeAsync<RiskConfirmationFile>(stream, _jsonOptions, cancellationToken).ConfigureAwait(false)
                ?? new RiskConfirmationFile();
        }
        catch (Exception exception) when (exception is IOException or UnauthorizedAccessException or JsonException)
        {
            await WriteLogAsync("Warning", "risk", "Risk confirmation store degraded", exception.Message, cancellationToken).ConfigureAwait(false);
            return new RiskConfirmationFile();
        }
    }

    private async Task SaveAsync(RiskConfirmationFile store, CancellationToken cancellationToken)
    {
        Directory.CreateDirectory(paths.Data);
        var tempPath = StorePath + ".tmp";
        await using (var stream = File.Create(tempPath))
        {
            await JsonSerializer.SerializeAsync(stream, store, _jsonOptions, cancellationToken).ConfigureAwait(false);
        }

        if (File.Exists(StorePath))
        {
            try
            {
                File.Replace(tempPath, StorePath, null);
                return;
            }
            catch (Exception exception) when (exception is IOException or UnauthorizedAccessException or PlatformNotSupportedException)
            {
                await WriteLogAsync("Warning", "risk", "Atomic risk confirmation save degraded", exception.Message, cancellationToken).ConfigureAwait(false);
                File.Delete(StorePath);
            }
        }

        File.Move(tempPath, StorePath);
    }

    private Task WriteLogAsync(string level, string category, string message, string? detail, CancellationToken cancellationToken)
    {
        return logService?.WriteAsync(level, category, message, detail, cancellationToken) ?? Task.CompletedTask;
    }

    private sealed class RiskConfirmationFile
    {
        public List<RiskConfirmationItem> Items { get; set; } = [];
    }

    private sealed record RiskConfirmationItem(
        string ToolId,
        string Operation,
        DateTimeOffset RememberedAt);
}
