using System.Text.Json;
using System.Text.Json.Serialization;

namespace YMhut.Box.Core.Downloads;

public enum DownloadState
{
    Queued,
    Running,
    Paused,
    Completed,
    Failed,
    Canceled
}

public sealed record DownloadSource(
    string Url,
    string DisplayName,
    string FileName,
    string SourceKind = "Official",
    string? Sha256 = null,
    string SourceLabel = "",
    string MirrorRegion = "",
    long? SizeBytes = null,
    string OriginalUrl = "",
    string ResolvedUrl = "",
    string ValidatedContentType = "",
    string PackageId = "")
{
    public string EffectiveLabel => string.IsNullOrWhiteSpace(SourceLabel) ? SourceKind : SourceLabel;

    public string EffectiveUrl => string.IsNullOrWhiteSpace(ResolvedUrl) ? Url : ResolvedUrl;
}

public sealed record DownloadOptions(
    string? TargetDirectory = null,
    string? TargetPath = null,
    string? InstallCommand = null,
    string? InstallArguments = null,
    bool IsInstaller = false,
    bool DeleteAfterInstall = false);

public sealed record DownloadSettings(
    string DefaultDirectory,
    int MaxConcurrentDownloads = 5)
{
    public int EffectiveMaxConcurrentDownloads => Math.Clamp(MaxConcurrentDownloads, 1, 5);
}

public sealed record DownloadItem(
    string Id,
    DownloadSource Source,
    string TargetPath,
    DownloadState State,
    long ReceivedBytes = 0,
    long? TotalBytes = null,
    double BytesPerSecond = 0,
    DateTimeOffset CreatedAt = default,
    DateTimeOffset UpdatedAt = default,
    string? Error = null,
    string? InstallCommand = null,
    string? InstallArguments = null,
    bool IsInstaller = false,
    bool DeleteAfterInstall = false,
    bool InstallLaunched = false,
    bool InstallCleanupCompleted = false,
    string PartialPath = "",
    string ETag = "",
    string LastModified = "",
    string AcceptRanges = "",
    long? ContentLength = null,
    string FinalUrl = "",
    bool ResumeSupported = false)
{
    [JsonIgnore]
    public string EffectivePartialPath => string.IsNullOrWhiteSpace(PartialPath) ? TargetPath + ".partial" : PartialPath;

    public static DownloadItem Create(
        DownloadSource source,
        string targetPath,
        string? installCommand = null,
        string? installArguments = null,
        bool isInstaller = false,
        bool deleteAfterInstall = false)
    {
        var now = DateTimeOffset.UtcNow;
        return new DownloadItem(
            Guid.NewGuid().ToString("N"),
            source,
            targetPath,
            DownloadState.Queued,
            CreatedAt: now,
            UpdatedAt: now,
            InstallCommand: installCommand,
            InstallArguments: installArguments,
            IsInstaller: isInstaller,
            DeleteAfterInstall: deleteAfterInstall,
            PartialPath: targetPath + ".partial");
    }

    public DownloadItem WithProgress(DownloadState state, long receivedBytes, long? totalBytes, double bytesPerSecond, string? error = null)
    {
        return this with
        {
            State = state,
            ReceivedBytes = Math.Max(0, receivedBytes),
            TotalBytes = totalBytes is > 0 ? totalBytes : null,
            BytesPerSecond = Math.Max(0, bytesPerSecond),
            UpdatedAt = DateTimeOffset.UtcNow,
            Error = error
        };
    }

    public DownloadItem WithResumeMetadata(
        string? eTag = null,
        string? lastModified = null,
        string? acceptRanges = null,
        long? contentLength = null,
        string? finalUrl = null,
        bool? resumeSupported = null)
    {
        return this with
        {
            PartialPath = string.IsNullOrWhiteSpace(PartialPath) ? TargetPath + ".partial" : PartialPath,
            ETag = string.IsNullOrWhiteSpace(eTag) ? ETag : eTag,
            LastModified = string.IsNullOrWhiteSpace(lastModified) ? LastModified : lastModified,
            AcceptRanges = string.IsNullOrWhiteSpace(acceptRanges) ? AcceptRanges : acceptRanges,
            ContentLength = contentLength is > 0 ? contentLength : ContentLength,
            FinalUrl = string.IsNullOrWhiteSpace(finalUrl) ? FinalUrl : finalUrl,
            ResumeSupported = resumeSupported ?? ResumeSupported,
            UpdatedAt = DateTimeOffset.UtcNow
        };
    }

    public DownloadItem WithInstallState(bool launched, bool cleanupCompleted = false)
    {
        return this with
        {
            InstallLaunched = launched,
            InstallCleanupCompleted = cleanupCompleted,
            UpdatedAt = DateTimeOffset.UtcNow
        };
    }
}

public sealed record DownloadProgressSnapshot(
    string Id,
    DownloadState State,
    long ReceivedBytes,
    long? TotalBytes,
    double BytesPerSecond,
    string? Error = null,
    string ETag = "",
    string LastModified = "",
    string AcceptRanges = "",
    long? ContentLength = null,
    string FinalUrl = "",
    bool RestartedFromZero = false)
{
    public double Progress
    {
        get
        {
            if (TotalBytes is not > 0)
            {
                return 0;
            }

            return Math.Clamp(ReceivedBytes / (double)TotalBytes.Value, 0, 1);
        }
    }
}

public static class DownloadFormat
{
    public static string FormatBytes(long bytes)
    {
        string[] units = ["B", "KB", "MB", "GB", "TB"];
        var value = Math.Max(0, bytes);
        var unit = 0;
        var scaled = (double)value;
        while (scaled >= 1024 && unit < units.Length - 1)
        {
            scaled /= 1024;
            unit++;
        }

        return unit == 0 ? $"{value} {units[unit]}" : $"{scaled:0.#} {units[unit]}";
    }

    public static string FormatSpeed(double bytesPerSecond)
    {
        return $"{FormatBytes((long)Math.Max(0, bytesPerSecond))}/s";
    }

    public static string FormatEta(long receivedBytes, long? totalBytes, double bytesPerSecond)
    {
        if (totalBytes is not > 0 ||
            receivedBytes <= 0 ||
            receivedBytes >= totalBytes.Value ||
            bytesPerSecond <= 1)
        {
            return "-";
        }

        var remaining = Math.Max(0, totalBytes.Value - receivedBytes);
        var eta = TimeSpan.FromSeconds(remaining / bytesPerSecond);
        if (eta.TotalHours >= 1)
        {
            return $"{(int)eta.TotalHours:0}h {eta.Minutes:00}m";
        }

        return eta.TotalMinutes >= 1
            ? $"{eta.Minutes:0}m {eta.Seconds:00}s"
            : $"{Math.Max(1, eta.Seconds):0}s";
    }
}

public static class DownloadHostProtocol
{
    public const string Version = "1";
    public const string Ready = "ready";
    public const string Start = "start";
    public const string Pause = "pause";
    public const string Resume = "resume";
    public const string Cancel = "cancel";
    public const string Progress = "progress";
    public const string Completed = "completed";
    public const string Failed = "failed";
    public const string Paused = "paused";
    public const string Canceled = "canceled";
    public const string Shutdown = "shutdown";
    public const string Ping = "ping";
    public const string Pong = "pong";

    private static readonly JsonSerializerOptions JsonOptions = new(JsonSerializerDefaults.Web)
    {
        Converters = { new JsonStringEnumConverter<DownloadState>() }
    };

    public static string Serialize(DownloadHostMessage message)
    {
        return JsonSerializer.Serialize(message, JsonOptions);
    }

    public static DownloadHostMessage? Deserialize(string line)
    {
        return JsonSerializer.Deserialize<DownloadHostMessage>(line, JsonOptions);
    }
}

public sealed record DownloadHostMessage(
    string Type,
    string RequestId = "",
    string Version = "",
    DownloadItem? Item = null,
    DownloadProgressSnapshot? Progress = null,
    string? Error = null);
