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

namespace YMhut.Box.Core.Media;

public interface IRemoteMediaCatalogService
{
    string CacheDirectory { get; }

    Task<RemoteMediaCatalogLoadResult> LoadAsync(bool forceRefresh = false, CancellationToken cancellationToken = default);

    Task<RemoteMediaCatalogLoadResult?> TryReadCacheAsync(CancellationToken cancellationToken = default);

    Task ClearCacheAsync(CancellationToken cancellationToken = default);
}

public sealed class RemoteMediaCatalogService(
    AppPaths paths,
    IApiManager apiManager,
    ILogService? logService = null) : IRemoteMediaCatalogService
{
    public static readonly Uri PrimaryConfigUri = new("https://update.ymhut.cn/media-types.json");

    private const string EndpointId = "media_types";
    private const string SnapshotFileName = "media-types.json";

    public string CacheDirectory => Path.Combine(paths.Cache, "remote-media");

    private string SnapshotPath => Path.Combine(CacheDirectory, SnapshotFileName);

    public async Task<RemoteMediaCatalogLoadResult> LoadAsync(bool forceRefresh = false, CancellationToken cancellationToken = default)
    {
        string? warning = null;
        try
        {
            var response = forceRefresh
                ? await apiManager.FetchUriAsync(EndpointId, AddCacheBuster(PrimaryConfigUri), string.Empty, cancellationToken).ConfigureAwait(false)
                : await apiManager.FetchAsync(EndpointId, string.Empty, cancellationToken).ConfigureAwait(false);

            if (!response.Success)
            {
                throw new InvalidOperationException(response.Error ?? "Remote media configuration request failed.");
            }

            var catalog = RemoteMediaCatalogParser.Parse(response.Content, response.FetchedAt);
            await WriteSnapshotAsync(response.Content, cancellationToken).ConfigureAwait(false);
            return new RemoteMediaCatalogLoadResult(catalog, RemoteMediaCatalogLoadSource.Remote);
        }
        catch (Exception exception) when (exception is HttpRequestException or IOException or JsonException or InvalidDataException or TaskCanceledException or InvalidOperationException)
        {
            warning = exception.Message;
            await WriteLogAsync("Warning", "remote-media", "Remote media configuration failed; trying local snapshot.", warning, cancellationToken).ConfigureAwait(false);
        }

        var cached = await TryReadCacheAsync(cancellationToken).ConfigureAwait(false);
        if (cached is not null)
        {
            return cached with { Warning = warning };
        }

        throw new InvalidOperationException(warning ?? "Remote media configuration is unavailable and no local snapshot exists.");
    }

    public async Task<RemoteMediaCatalogLoadResult?> TryReadCacheAsync(CancellationToken cancellationToken = default)
    {
        if (!File.Exists(SnapshotPath))
        {
            return null;
        }

        try
        {
            var content = await File.ReadAllTextAsync(SnapshotPath, cancellationToken).ConfigureAwait(false);
            var loadedAt = File.GetLastWriteTimeUtc(SnapshotPath);
            var catalog = RemoteMediaCatalogParser.Parse(content, new DateTimeOffset(loadedAt, TimeSpan.Zero));
            return new RemoteMediaCatalogLoadResult(catalog, RemoteMediaCatalogLoadSource.Cache);
        }
        catch (Exception exception) when (exception is IOException or JsonException or InvalidDataException)
        {
            await WriteLogAsync("Warning", "remote-media", "Remote media cache snapshot is unreadable.", exception.Message, cancellationToken).ConfigureAwait(false);
            return null;
        }
    }

    public Task ClearCacheAsync(CancellationToken cancellationToken = default)
    {
        cancellationToken.ThrowIfCancellationRequested();
        if (!Directory.Exists(CacheDirectory))
        {
            return Task.CompletedTask;
        }

        NormalizeAttributes(CacheDirectory);
        Directory.Delete(CacheDirectory, recursive: true);
        return Task.CompletedTask;
    }

    private async Task WriteSnapshotAsync(string content, CancellationToken cancellationToken)
    {
        Directory.CreateDirectory(CacheDirectory);
        await File.WriteAllTextAsync(SnapshotPath, content, cancellationToken).ConfigureAwait(false);
    }

    private static Uri AddCacheBuster(Uri uri)
    {
        var builder = new UriBuilder(uri);
        var token = $"_={DateTimeOffset.UtcNow.ToUnixTimeMilliseconds()}";
        builder.Query = string.IsNullOrWhiteSpace(builder.Query)
            ? token
            : builder.Query.TrimStart('?') + "&" + token;
        return builder.Uri;
    }

    private static void NormalizeAttributes(string directory)
    {
        foreach (var path in Directory.EnumerateFileSystemEntries(directory, "*", SearchOption.AllDirectories))
        {
            File.SetAttributes(path, FileAttributes.Normal);
        }

        File.SetAttributes(directory, FileAttributes.Normal);
    }

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