using System.Globalization;
using System.Net;
using System.Net.Http.Headers;
using System.Net.Http.Json;
using System.Text.Json;
using System.Text.RegularExpressions;
using Ikar.Shared;

namespace Ikar.Server.Infrastructure.Updates;

public sealed class AndroidAppUpdateService(
    IHostEnvironment hostEnvironment,
    IHttpClientFactory httpClientFactory,
    ILogger<AndroidAppUpdateService> logger)
{
    private const string ArgusBaseUrl = "https://argus.kusoft.xyz";
    private const string ArgusSlug = "ikar-android";
    private const string ArgusChannel = "stable";
    private const string ArgusPlatform = "android";
    private static readonly JsonSerializerOptions JsonOptions = new(JsonSerializerDefaults.Web);
    private static readonly Regex VersionCodeFromFileNameRegex = new(
        @"ikar-android-(\d+)\.apk$",
        RegexOptions.Compiled | RegexOptions.CultureInvariant | RegexOptions.IgnoreCase);

    private string UpdatesRootPath => Path.Combine(hostEnvironment.ContentRootPath, "Data", "AppUpdates", "Android");

    private string MetadataPath => Path.Combine(UpdatesRootPath, "latest.json");

    public void EnsureStorage()
    {
        Directory.CreateDirectory(UpdatesRootPath);
    }

    public async Task<AndroidAppReleaseDto?> GetLatestReleaseAsync(CancellationToken cancellationToken = default)
    {
        var argusManifest = await TryGetArgusManifestAsync(cancellationToken);
        if (argusManifest is not null)
        {
            return ApplyMandatoryLatestPolicy(argusManifest.Release);
        }

        var localRelease = GetLatestLocalRelease();
        return localRelease is null ? null : ApplyMandatoryLatestPolicy(localRelease);
    }

    public async Task<AndroidAppDownloadResolution?> ResolvePackageAsync(string fileName, CancellationToken cancellationToken = default)
    {
        var argusManifest = await TryGetArgusManifestAsync(cancellationToken);
        if (argusManifest is not null && MatchesArgusFileName(argusManifest, fileName))
        {
            return AndroidAppDownloadResolution.ForRedirect(
                argusManifest.Release.DownloadPath,
                argusManifest.OriginalFileName);
        }

        var latest = GetLocalMetadata();
        if (latest is null)
        {
            return null;
        }

        if (!string.Equals(latest.PackageFileName, fileName, StringComparison.Ordinal))
        {
            return null;
        }

        var physicalPath = Path.Combine(UpdatesRootPath, latest.PackageFileName);
        if (!File.Exists(physicalPath))
        {
            return null;
        }

        return AndroidAppDownloadResolution.ForPhysical(physicalPath, latest.PackageFileName);
    }

    public async Task<AndroidAppDownloadResolution?> ResolveLatestPackageAsync(CancellationToken cancellationToken = default)
    {
        var argusManifest = await TryGetArgusManifestAsync(cancellationToken);
        if (argusManifest is not null)
        {
            return AndroidAppDownloadResolution.ForRedirect(
                argusManifest.Release.DownloadPath,
                argusManifest.OriginalFileName);
        }

        var latest = GetLocalMetadata();
        if (latest is null)
        {
            return null;
        }

        var physicalPath = Path.Combine(UpdatesRootPath, latest.PackageFileName);
        if (!File.Exists(physicalPath))
        {
            return null;
        }

        return AndroidAppDownloadResolution.ForPhysical(physicalPath, latest.PackageFileName);
    }

    private async Task<ArgusAndroidRelease?> TryGetArgusManifestAsync(CancellationToken cancellationToken)
    {
        try
        {
            using var client = httpClientFactory.CreateClient();
            using var request = new HttpRequestMessage(
                HttpMethod.Get,
                $"{ArgusBaseUrl.TrimEnd('/')}/api/apps/{ArgusSlug}/manifest?platform={ArgusPlatform}&channel={ArgusChannel}");
            request.Headers.CacheControl = new CacheControlHeaderValue
            {
                NoCache = true,
                NoStore = true
            };

            using var response = await client.SendAsync(request, cancellationToken);
            if (response.StatusCode == HttpStatusCode.NotFound)
            {
                return null;
            }

            if (!response.IsSuccessStatusCode)
            {
                logger.LogWarning(
                    "Argus manifest request failed with status code {StatusCode}.",
                    (int)response.StatusCode);
                return null;
            }

            var manifest = await response.Content.ReadFromJsonAsync<ArgusManifestDto>(JsonOptions, cancellationToken);
            if (manifest?.Release is null)
            {
                logger.LogWarning("Argus manifest response was empty or missing release metadata.");
                return null;
            }

            if (!TryMapArgusRelease(manifest.Release, out var release, out var originalFileName))
            {
                logger.LogWarning(
                    "Argus manifest could not be mapped to AndroidAppReleaseDto. Version={Version}, OriginalFileName={OriginalFileName}.",
                    manifest.Release.Version,
                    manifest.Release.OriginalFileName);
                return null;
            }

            return new ArgusAndroidRelease(release, originalFileName);
        }
        catch (Exception ex)
        {
            logger.LogWarning(ex, "Failed to load Android app release metadata from Argus.");
            return null;
        }
    }

    private static bool TryMapArgusRelease(
        ArgusReleaseDto argusRelease,
        out AndroidAppReleaseDto release,
        out string originalFileName)
    {
        release = null!;
        originalFileName = argusRelease.OriginalFileName;

        if (!TryParseVersion(argusRelease.Version, argusRelease.OriginalFileName, out var versionCode, out var versionName))
        {
            return false;
        }

        var downloadPath = BuildAbsoluteArgusUrl(argusRelease.DownloadPath);
        if (downloadPath is null)
        {
            return false;
        }

        release = new AndroidAppReleaseDto(
            versionCode,
            versionName,
            downloadPath,
            argusRelease.PublishedAt,
            argusRelease.Notes);

        return true;
    }

    private static bool TryParseVersion(
        string rawVersion,
        string? originalFileName,
        out int versionCode,
        out string versionName)
    {
        versionCode = 0;
        versionName = rawVersion.Trim();

        var buildSeparatorIndex = versionName.IndexOf('+');
        if (buildSeparatorIndex >= 0)
        {
            var parsedVersionName = versionName[..buildSeparatorIndex].Trim();
            var buildMetadata = versionName[(buildSeparatorIndex + 1)..].Trim();

            if (!string.IsNullOrWhiteSpace(parsedVersionName))
            {
                versionName = parsedVersionName;
            }

            if (int.TryParse(buildMetadata, NumberStyles.Integer, CultureInfo.InvariantCulture, out var parsedVersionCode) &&
                parsedVersionCode > 0)
            {
                versionCode = parsedVersionCode;
            }
        }

        if (versionCode <= 0 &&
            !string.IsNullOrWhiteSpace(originalFileName) &&
            TryParseVersionCodeFromFileName(originalFileName, out var parsedFromFileName))
        {
            versionCode = parsedFromFileName;
        }

        return versionCode > 0 && !string.IsNullOrWhiteSpace(versionName);
    }

    private static bool TryParseVersionCodeFromFileName(string fileName, out int versionCode)
    {
        versionCode = 0;
        var match = VersionCodeFromFileNameRegex.Match(fileName);
        if (!match.Success)
        {
            return false;
        }

        return int.TryParse(match.Groups[1].Value, NumberStyles.Integer, CultureInfo.InvariantCulture, out versionCode) &&
               versionCode > 0;
    }

    private static string? BuildAbsoluteArgusUrl(string? rawPath)
    {
        if (string.IsNullOrWhiteSpace(rawPath))
        {
            return null;
        }

        if (Uri.TryCreate(rawPath, UriKind.Absolute, out var absoluteUri) &&
            (absoluteUri.Scheme == Uri.UriSchemeHttp || absoluteUri.Scheme == Uri.UriSchemeHttps))
        {
            return absoluteUri.ToString();
        }

        return new Uri(new Uri($"{ArgusBaseUrl.TrimEnd('/')}/"), rawPath.TrimStart('/')).ToString();
    }

    private static bool MatchesArgusFileName(ArgusAndroidRelease argusManifest, string requestedFileName)
    {
        var normalizedRequested = requestedFileName.Trim();
        if (string.Equals(normalizedRequested, argusManifest.OriginalFileName, StringComparison.OrdinalIgnoreCase))
        {
            return true;
        }

        var generatedFileName = BuildExpectedFileName(argusManifest.Release.VersionCode);
        return string.Equals(normalizedRequested, generatedFileName, StringComparison.OrdinalIgnoreCase);
    }

    private AndroidAppReleaseDto? GetLatestLocalRelease()
    {
        var metadata = GetLocalMetadata();
        if (metadata is null)
        {
            return null;
        }

        var packagePath = Path.Combine(UpdatesRootPath, metadata.PackageFileName);
        if (!File.Exists(packagePath))
        {
            return null;
        }

        return new AndroidAppReleaseDto(
            metadata.VersionCode,
            metadata.VersionName,
            $"/api/app-updates/android/download/{Uri.EscapeDataString(metadata.PackageFileName)}",
            metadata.PublishedAt,
            metadata.Notes);
    }

    private static AndroidAppReleaseDto ApplyMandatoryLatestPolicy(AndroidAppReleaseDto release)
    {
        return release with
        {
            IsRequired = true,
            MinimumSupportedVersionCode = release.VersionCode
        };
    }

    private AndroidAppReleaseMetadata? GetLocalMetadata()
    {
        if (!File.Exists(MetadataPath))
        {
            return null;
        }

        return JsonSerializer.Deserialize<AndroidAppReleaseMetadata>(File.ReadAllText(MetadataPath), JsonOptions);
    }

    private static string BuildExpectedFileName(int versionCode) => $"ikar-android-{versionCode}.apk";

    private sealed record ArgusManifestDto(
        string Slug,
        string Name,
        string Summary,
        string Description,
        string? RepositoryUrl,
        string? HomepageUrl,
        ArgusReleaseDto Release);

    private sealed record ArgusReleaseDto(
        Guid Id,
        string Version,
        string Channel,
        string Platform,
        string PackageKind,
        string DownloadPath,
        string OriginalFileName,
        string ContentType,
        long PackageSizeBytes,
        string Sha256,
        DateTimeOffset PublishedAt,
        string? Notes);

    private sealed record ArgusAndroidRelease(AndroidAppReleaseDto Release, string OriginalFileName);

    public sealed record AndroidAppDownloadResolution(
        string FileName,
        string? PhysicalPath,
        string? RedirectUrl)
    {
        public static AndroidAppDownloadResolution ForPhysical(string physicalPath, string fileName) =>
            new(fileName, physicalPath, null);

        public static AndroidAppDownloadResolution ForRedirect(string redirectUrl, string fileName) =>
            new(fileName, null, redirectUrl);
    }

    public sealed record AndroidAppReleaseMetadata(
        int VersionCode,
        string VersionName,
        string PackageFileName,
        DateTimeOffset PublishedAt,
        string? Notes);
}
