using System.Net.Http.Headers;
using System.Text;

namespace YMhut.Box.Core.Downloads;

public interface IDirectDownloadValidator
{
    Task<DirectDownloadValidationResult> ValidateAsync(string input, CancellationToken cancellationToken = default);
}

public sealed record DirectDownloadValidationResult(
    bool IsDirectDownload,
    string Url,
    string OriginalUrl = "",
    string ResolvedUrl = "",
    string ContentType = "",
    string SuggestedFileName = "",
    string Message = "",
    bool WasInternetShortcut = false)
{
    public string EffectiveUrl => string.IsNullOrWhiteSpace(ResolvedUrl) ? Url : ResolvedUrl;
}

public sealed class DirectDownloadValidator(HttpClient? client = null) : IDirectDownloadValidator, IDisposable
{
    private const int ProbeBytes = 4096;
    private readonly HttpClient _client = client ?? CreateDefaultClient();
    private readonly bool _disposeClient = client is null;

    public async Task<DirectDownloadValidationResult> ValidateAsync(string input, CancellationToken cancellationToken = default)
    {
        var value = input.Trim();
        if (TryParseInternetShortcut(value, out var shortcutUrl))
        {
            var nested = await ValidateAsync(shortcutUrl, cancellationToken).ConfigureAwait(false);
            return nested with
            {
                OriginalUrl = value,
                WasInternetShortcut = true,
                Message = nested.IsDirectDownload
                    ? "Internet shortcut resolved to a direct download URL."
                    : nested.Message
            };
        }

        if (!Uri.TryCreate(value, UriKind.Absolute, out var uri) ||
            uri.Scheme is not ("http" or "https"))
        {
            return new DirectDownloadValidationResult(false, value, Message: "Enter a valid HTTP/HTTPS download URL.");
        }

        if (LooksLikeInternetShortcutName(uri))
        {
            var shortcut = await TryReadShortcutAsync(uri, cancellationToken).ConfigureAwait(false);
            if (!string.IsNullOrWhiteSpace(shortcut) && TryParseInternetShortcut(shortcut, out var resolved))
            {
                var nested = await ValidateAsync(resolved, cancellationToken).ConfigureAwait(false);
                return nested with
                {
                    OriginalUrl = uri.ToString(),
                    WasInternetShortcut = true,
                    Message = nested.IsDirectDownload
                        ? "Internet shortcut resolved to a direct download URL."
                        : nested.Message
                };
            }

            return new DirectDownloadValidationResult(
                false,
                uri.ToString(),
                Message: "This link points to an Internet shortcut instead of a downloadable file.",
                WasInternetShortcut: true);
        }

        try
        {
            using var request = new HttpRequestMessage(HttpMethod.Get, uri);
            request.Headers.Range = new RangeHeaderValue(0, ProbeBytes - 1);
            using var response = await _client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, cancellationToken)
                .ConfigureAwait(false);
            response.EnsureSuccessStatusCode();

            var resolvedUri = response.RequestMessage?.RequestUri?.ToString() ?? uri.ToString();
            var contentType = response.Content.Headers.ContentType?.MediaType ?? string.Empty;
            var contentDispositionName = response.Content.Headers.ContentDisposition?.FileNameStar ??
                response.Content.Headers.ContentDisposition?.FileName;
            var suggestedName = CleanHeaderFileName(contentDispositionName);
            if (string.IsNullOrWhiteSpace(suggestedName))
            {
                suggestedName = Path.GetFileName(new Uri(resolvedUri).LocalPath);
            }

            var probe = await ReadProbeAsync(response, cancellationToken).ConfigureAwait(false);
            var textProbe = LooksTextual(contentType, probe)
                ? Encoding.UTF8.GetString(probe)
                : string.Empty;

            if (IsBlockedContent(contentType, suggestedName, textProbe, out var message))
            {
                if (TryParseInternetShortcut(textProbe, out var nestedUrl))
                {
                    var nested = await ValidateAsync(nestedUrl, cancellationToken).ConfigureAwait(false);
                    return nested with
                    {
                        OriginalUrl = uri.ToString(),
                        WasInternetShortcut = true,
                        Message = nested.IsDirectDownload
                            ? "Internet shortcut resolved to a direct download URL."
                            : nested.Message
                    };
                }

                return new DirectDownloadValidationResult(
                    false,
                    uri.ToString(),
                    ResolvedUrl: resolvedUri,
                    ContentType: contentType,
                    SuggestedFileName: suggestedName,
                    Message: message);
            }

            return new DirectDownloadValidationResult(
                true,
                uri.ToString(),
                ResolvedUrl: resolvedUri,
                ContentType: contentType,
                SuggestedFileName: suggestedName,
                Message: "Direct download URL verified.");
        }
        catch (Exception exception)
        {
            return new DirectDownloadValidationResult(
                false,
                uri.ToString(),
                Message: $"Could not verify the download link: {exception.Message}");
        }
    }

    public void Dispose()
    {
        if (_disposeClient)
        {
            _client.Dispose();
        }
    }

    public static bool TryParseInternetShortcut(string content, out string url)
    {
        url = string.Empty;
        foreach (var rawLine in content.Replace("\r\n", "\n", StringComparison.Ordinal).Split('\n'))
        {
            var line = rawLine.Trim();
            if (!line.StartsWith("URL=", StringComparison.OrdinalIgnoreCase))
            {
                continue;
            }

            var value = line[4..].Trim();
            if (Uri.TryCreate(value, UriKind.Absolute, out var uri) &&
                uri.Scheme is "http" or "https")
            {
                url = uri.ToString();
                return true;
            }
        }

        return false;
    }

    private static HttpClient CreateDefaultClient()
    {
        var client = new HttpClient(new HttpClientHandler
        {
            AllowAutoRedirect = true,
            MaxAutomaticRedirections = 8
        })
        {
            Timeout = TimeSpan.FromSeconds(20)
        };
        client.DefaultRequestHeaders.UserAgent.ParseAdd("YMhutBox/2.0 DownloadValidator");
        return client;
    }

    private static async Task<string> TryReadShortcutAsync(Uri uri, CancellationToken cancellationToken)
    {
        try
        {
            using var client = CreateDefaultClient();
            using var request = new HttpRequestMessage(HttpMethod.Get, uri);
            request.Headers.Range = new RangeHeaderValue(0, ProbeBytes - 1);
            using var response = await client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, cancellationToken)
                .ConfigureAwait(false);
            if (!response.IsSuccessStatusCode)
            {
                return string.Empty;
            }

            var bytes = await ReadProbeAsync(response, cancellationToken).ConfigureAwait(false);
            return Encoding.UTF8.GetString(bytes);
        }
        catch
        {
            return string.Empty;
        }
    }

    private static async Task<byte[]> ReadProbeAsync(HttpResponseMessage response, CancellationToken cancellationToken)
    {
        await using var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
        using var memory = new MemoryStream();
        var buffer = new byte[ProbeBytes];
        var read = await stream.ReadAsync(buffer.AsMemory(0, buffer.Length), cancellationToken).ConfigureAwait(false);
        if (read > 0)
        {
            memory.Write(buffer, 0, read);
        }

        return memory.ToArray();
    }

    private static bool IsBlockedContent(string contentType, string fileName, string probe, out string message)
    {
        var lowerType = contentType.ToLowerInvariant();
        var lowerName = fileName.ToLowerInvariant();
        var text = probe.TrimStart('\uFEFF', ' ', '\t', '\r', '\n');

        if (lowerName.EndsWith(".url", StringComparison.OrdinalIgnoreCase) ||
            lowerType.Contains("internet-shortcut", StringComparison.OrdinalIgnoreCase))
        {
            message = "This is an Internet shortcut, not the actual downloadable file.";
            return true;
        }

        if (lowerType.Contains("text/html", StringComparison.OrdinalIgnoreCase) ||
            text.StartsWith("<!doctype html", StringComparison.OrdinalIgnoreCase) ||
            text.StartsWith("<html", StringComparison.OrdinalIgnoreCase))
        {
            message = "This link opened a web page instead of a direct file download.";
            return true;
        }

        if (LooksLikeRedirectText(text))
        {
            message = "This text response looks like a redirect or shortcut, not a direct file download.";
            return true;
        }

        message = string.Empty;
        return false;
    }

    private static bool LooksLikeRedirectText(string text)
    {
        if (string.IsNullOrWhiteSpace(text))
        {
            return false;
        }

        return text.Contains("[InternetShortcut]", StringComparison.OrdinalIgnoreCase) ||
            text.StartsWith("URL=", StringComparison.OrdinalIgnoreCase) ||
            text.Contains("window.location", StringComparison.OrdinalIgnoreCase) ||
            text.Contains("location.href", StringComparison.OrdinalIgnoreCase) ||
            text.Contains("http-equiv=\"refresh\"", StringComparison.OrdinalIgnoreCase);
    }

    private static bool LooksTextual(string contentType, byte[] bytes)
    {
        if (contentType.StartsWith("text/", StringComparison.OrdinalIgnoreCase) ||
            contentType.Contains("json", StringComparison.OrdinalIgnoreCase) ||
            contentType.Contains("xml", StringComparison.OrdinalIgnoreCase))
        {
            return true;
        }

        return bytes.Length > 0 && bytes.Take(Math.Min(bytes.Length, 128)).All(value =>
            value is 9 or 10 or 13 || value >= 32);
    }

    private static bool LooksLikeInternetShortcutName(Uri uri)
    {
        return uri.LocalPath.EndsWith(".url", StringComparison.OrdinalIgnoreCase);
    }

    private static string CleanHeaderFileName(string? value)
    {
        if (string.IsNullOrWhiteSpace(value))
        {
            return string.Empty;
        }

        return value.Trim().Trim('"');
    }
}
