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

namespace YMhut.Box.Core.Downloads;

public interface IDownloadQueueStore
{
    Task<IReadOnlyList<DownloadItem>> LoadAsync(CancellationToken cancellationToken = default);

    Task SaveAsync(IReadOnlyList<DownloadItem> items, CancellationToken cancellationToken = default);

    Task<DownloadSettings> LoadSettingsAsync(CancellationToken cancellationToken = default);

    Task SaveSettingsAsync(DownloadSettings settings, CancellationToken cancellationToken = default);
}

public sealed class DownloadQueueStore(AppPaths paths) : IDownloadQueueStore
{
    private static readonly JsonSerializerOptions JsonOptions = new(JsonSerializerDefaults.Web)
    {
        WriteIndented = true,
        Converters = { new JsonStringEnumConverter<DownloadState>() }
    };

    private readonly string _path = Path.Combine(paths.Data, "downloads.json");
    private readonly string _settingsPath = Path.Combine(paths.Data, "download-settings.json");
    private readonly SemaphoreSlim _gate = new(1, 1);

    public async Task<IReadOnlyList<DownloadItem>> LoadAsync(CancellationToken cancellationToken = default)
    {
        await _gate.WaitAsync(cancellationToken).ConfigureAwait(false);
        try
        {
            if (!File.Exists(_path))
            {
                return [];
            }

            await using var stream = File.OpenRead(_path);
            return await JsonSerializer.DeserializeAsync<List<DownloadItem>>(stream, JsonOptions, cancellationToken)
                    .ConfigureAwait(false)
                ?? [];
        }
        catch
        {
            return [];
        }
        finally
        {
            _gate.Release();
        }
    }

    public async Task SaveAsync(IReadOnlyList<DownloadItem> items, CancellationToken cancellationToken = default)
    {
        await _gate.WaitAsync(cancellationToken).ConfigureAwait(false);
        try
        {
            Directory.CreateDirectory(Path.GetDirectoryName(_path)!);
            var temp = _path + ".tmp";
            await using (var stream = File.Create(temp))
            {
                await JsonSerializer.SerializeAsync(stream, items, JsonOptions, cancellationToken).ConfigureAwait(false);
            }

            File.Copy(temp, _path, overwrite: true);
            File.Delete(temp);
        }
        finally
        {
            _gate.Release();
        }
    }

    public async Task<DownloadSettings> LoadSettingsAsync(CancellationToken cancellationToken = default)
    {
        await _gate.WaitAsync(cancellationToken).ConfigureAwait(false);
        try
        {
            var fallback = DefaultSettings();
            if (!File.Exists(_settingsPath))
            {
                return fallback;
            }

            await using var stream = File.OpenRead(_settingsPath);
            var settings = await JsonSerializer.DeserializeAsync<DownloadSettings>(stream, JsonOptions, cancellationToken)
                    .ConfigureAwait(false)
                ?? fallback;

            var directory = string.IsNullOrWhiteSpace(settings.DefaultDirectory)
                ? fallback.DefaultDirectory
                : settings.DefaultDirectory;
            return settings with
            {
                DefaultDirectory = directory,
                MaxConcurrentDownloads = settings.EffectiveMaxConcurrentDownloads
            };
        }
        catch
        {
            return DefaultSettings();
        }
        finally
        {
            _gate.Release();
        }
    }

    public async Task SaveSettingsAsync(DownloadSettings settings, CancellationToken cancellationToken = default)
    {
        await _gate.WaitAsync(cancellationToken).ConfigureAwait(false);
        try
        {
            var normalized = settings with
            {
                DefaultDirectory = string.IsNullOrWhiteSpace(settings.DefaultDirectory)
                    ? DefaultSettings().DefaultDirectory
                    : settings.DefaultDirectory,
                MaxConcurrentDownloads = settings.EffectiveMaxConcurrentDownloads
            };
            Directory.CreateDirectory(Path.GetDirectoryName(_settingsPath)!);
            var temp = _settingsPath + ".tmp";
            await using (var stream = File.Create(temp))
            {
                await JsonSerializer.SerializeAsync(stream, normalized, JsonOptions, cancellationToken).ConfigureAwait(false);
            }

            File.Copy(temp, _settingsPath, overwrite: true);
            File.Delete(temp);
        }
        finally
        {
            _gate.Release();
        }
    }

    private DownloadSettings DefaultSettings()
    {
        return new DownloadSettings(Path.Combine(paths.Data, "Downloads"), 5);
    }
}

public interface IDownloadHostProcessService : IDisposable
{
    event EventHandler<DownloadProgressSnapshot>? ProgressChanged;

    Task StartAsync(DownloadItem item, CancellationToken cancellationToken = default);

    Task PauseAsync(string id, CancellationToken cancellationToken = default);

    Task ResumeAsync(DownloadItem item, CancellationToken cancellationToken = default);

    Task CancelAsync(string id, CancellationToken cancellationToken = default);
}

public interface IDownloadManagerService
{
    event EventHandler? ItemsChanged;

    IReadOnlyList<DownloadItem> Items { get; }

    DownloadSettings Settings { get; }

    Task InitializeAsync(CancellationToken cancellationToken = default);

    Task<DownloadItem> EnqueueAsync(DownloadSource source, string? targetDirectory = null, string? installCommand = null, string? installArguments = null, CancellationToken cancellationToken = default);

    Task<DownloadItem> EnqueueAsync(DownloadSource source, DownloadOptions options, CancellationToken cancellationToken = default);

    Task UpdateSettingsAsync(DownloadSettings settings, CancellationToken cancellationToken = default);

    Task StartAsync(string id, CancellationToken cancellationToken = default);

    Task PauseAsync(string id, CancellationToken cancellationToken = default);

    Task ResumeAsync(string id, CancellationToken cancellationToken = default);

    Task CancelAsync(string id, CancellationToken cancellationToken = default);

    Task RetryAsync(string id, CancellationToken cancellationToken = default);

    Task ClearCompletedAsync(CancellationToken cancellationToken = default);

    Task MarkInstallLaunchedAsync(string id, bool cleanupCompleted = false, CancellationToken cancellationToken = default);

    Task TryCleanupAfterInstallAsync(string id, CancellationToken cancellationToken = default);
}
