using System.IO.Compression;
using System.Text.Json;
using System.Text.Json.Serialization;
using Scry.Core.Models;

namespace Scry.Core.Scryfall;

public class ScryfallClient : IDisposable
{
    private readonly HttpClient _httpClient;
    private const string BulkDataUrl = "https://api.scryfall.com/bulk-data";
    private const string SetsUrl = "https://api.scryfall.com/sets";
    
    public ScryfallClient(HttpClient? httpClient = null)
    {
        _httpClient = httpClient ?? new HttpClient();
        _httpClient.DefaultRequestHeaders.UserAgent.ParseAdd("Scry/1.0 (MTG Card Scanner)");
    }
    
    public async Task<BulkDataInfo?> GetBulkDataInfoAsync(string type = "unique_artwork", CancellationToken ct = default)
    {
        var response = await _httpClient.GetStringAsync(BulkDataUrl, ct);
        var bulkData = JsonSerializer.Deserialize<BulkDataResponse>(response, JsonOptions);
        
        return bulkData?.Data?.FirstOrDefault(d => 
            d.Type?.Equals(type, StringComparison.OrdinalIgnoreCase) == true);
    }
    
    public async Task<List<ScryfallSet>> GetAllSetsAsync(CancellationToken ct = default)
    {
        var sets = new List<ScryfallSet>();
        var url = SetsUrl;
        
        while (!string.IsNullOrEmpty(url))
        {
            var response = await _httpClient.GetStringAsync(url, ct);
            var setsResponse = JsonSerializer.Deserialize<SetsResponse>(response, JsonOptions);
            
            if (setsResponse?.Data != null)
            {
                sets.AddRange(setsResponse.Data);
            }
            
            url = setsResponse?.NextPage;
        }
        
        return sets;
    }
    
    public async IAsyncEnumerable<ScryfallCard> StreamBulkDataAsync(
        string downloadUri, 
        [System.Runtime.CompilerServices.EnumeratorCancellation] CancellationToken ct = default)
    {
        using var response = await _httpClient.GetAsync(downloadUri, HttpCompletionOption.ResponseHeadersRead, ct);
        response.EnsureSuccessStatusCode();
        
        await using var stream = await response.Content.ReadAsStreamAsync(ct);
        
        Stream dataStream = stream;
        if (downloadUri.EndsWith(".gz", StringComparison.OrdinalIgnoreCase))
        {
            dataStream = new GZipStream(stream, CompressionMode.Decompress);
        }
        
        await foreach (var card in JsonSerializer.DeserializeAsyncEnumerable<ScryfallCard>(dataStream, JsonOptions, ct))
        {
            if (card != null)
            {
                yield return card;
            }
        }
    }
    
    public async Task<byte[]?> DownloadImageAsync(string imageUri, CancellationToken ct = default)
    {
        try
        {
            return await _httpClient.GetByteArrayAsync(imageUri, ct);
        }
        catch (HttpRequestException)
        {
            return null;
        }
    }
    
    public void Dispose()
    {
        _httpClient.Dispose();
    }
    
    private static readonly JsonSerializerOptions JsonOptions = new()
    {
        PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower,
        PropertyNameCaseInsensitive = true
    };
}

#region API Response Models

public record BulkDataResponse
{
    public List<BulkDataInfo>? Data { get; init; }
}

public record BulkDataInfo
{
    public string? Id { get; init; }
    public string? Type { get; init; }
    public string? Name { get; init; }
    public string? DownloadUri { get; init; }
    public DateTime? UpdatedAt { get; init; }
    public long? Size { get; init; }
}

public record SetsResponse
{
    public List<ScryfallSet>? Data { get; init; }
    public bool HasMore { get; init; }
    public string? NextPage { get; init; }
}

public record ScryfallSet
{
    public string? Id { get; init; }
    public string? Code { get; init; }
    public string? Name { get; init; }
    public string? SetType { get; init; }
    public string? ReleasedAt { get; init; }
    public int? CardCount { get; init; }
    public string? IconSvgUri { get; init; }
    public bool Digital { get; init; }
    public string? ParentSetCode { get; init; }
    public string? Block { get; init; }
}

public record ScryfallCard
{
    // Core identifiers
    public string? Id { get; init; }
    public string? OracleId { get; init; }
    
    // Oracle/game card fields
    public string? Name { get; init; }
    public string? ManaCost { get; init; }
    public double? Cmc { get; init; }
    public string? TypeLine { get; init; }
    public string? OracleText { get; init; }
    public List<string>? Colors { get; init; }
    public List<string>? ColorIdentity { get; init; }
    public List<string>? Keywords { get; init; }
    public bool Reserved { get; init; }
    public Dictionary<string, string>? Legalities { get; init; }
    public string? Power { get; init; }
    public string? Toughness { get; init; }
    
    // Printing-specific fields
    public string? Set { get; init; }
    public string? SetId { get; init; }
    public string? SetName { get; init; }
    public string? CollectorNumber { get; init; }
    public string? Rarity { get; init; }
    public string? Artist { get; init; }
    public string? IllustrationId { get; init; }
    public string? Lang { get; init; }
    
    // Images and prices
    public ImageUris? ImageUris { get; init; }
    public Prices? Prices { get; init; }
    
    // Multi-face cards
    public List<CardFace>? CardFaces { get; init; }
}

public record ImageUris
{
    public string? Small { get; init; }
    public string? Normal { get; init; }
    public string? Large { get; init; }
    public string? Png { get; init; }
    public string? ArtCrop { get; init; }
    public string? BorderCrop { get; init; }
}

public record Prices
{
    public string? Usd { get; init; }
    public string? UsdFoil { get; init; }
    public string? Eur { get; init; }
}

public record CardFace
{
    public string? Name { get; init; }
    public string? ManaCost { get; init; }
    public string? TypeLine { get; init; }
    public string? OracleText { get; init; }
    public List<string>? Colors { get; init; }
    public string? Power { get; init; }
    public string? Toughness { get; init; }
    public ImageUris? ImageUris { get; init; }
}

#endregion

#region Extension Methods

public static class ScryfallCardExtensions
{
    /// <summary>
    /// Extracts the Oracle (abstract game card) from a Scryfall card.
    /// </summary>
    public static Oracle ToOracle(this ScryfallCard scryfall)
    {
        return new Oracle
        {
            Id = scryfall.OracleId ?? scryfall.Id ?? Guid.NewGuid().ToString(),
            Name = scryfall.Name ?? "Unknown",
            ManaCost = scryfall.ManaCost,
            Cmc = scryfall.Cmc,
            TypeLine = scryfall.TypeLine,
            OracleText = scryfall.OracleText,
            Colors = scryfall.Colors != null ? JsonSerializer.Serialize(scryfall.Colors) : null,
            ColorIdentity = scryfall.ColorIdentity != null ? JsonSerializer.Serialize(scryfall.ColorIdentity) : null,
            Keywords = scryfall.Keywords != null ? JsonSerializer.Serialize(scryfall.Keywords) : null,
            Reserved = scryfall.Reserved,
            Legalities = scryfall.Legalities != null ? JsonSerializer.Serialize(scryfall.Legalities) : null,
            Power = scryfall.Power,
            Toughness = scryfall.Toughness,
        };
    }
    
    /// <summary>
    /// Converts a Scryfall card to a Card (printing) model.
    /// Note: Hash must be computed separately and set on the returned Card.
    /// </summary>
    public static Card ToCard(this ScryfallCard scryfall)
    {
        var imageUris = scryfall.ImageUris ?? scryfall.CardFaces?.FirstOrDefault()?.ImageUris;
        
        return new Card
        {
            Id = scryfall.Id ?? Guid.NewGuid().ToString(),
            OracleId = scryfall.OracleId ?? scryfall.Id ?? Guid.NewGuid().ToString(),
            SetId = scryfall.SetId ?? "",
            SetCode = scryfall.Set,
            SetName = scryfall.SetName,
            Name = scryfall.Name ?? "Unknown",
            CollectorNumber = scryfall.CollectorNumber,
            Rarity = scryfall.Rarity,
            Artist = scryfall.Artist,
            IllustrationId = scryfall.IllustrationId,
            ImageUri = imageUris?.Normal,
            Lang = scryfall.Lang,
            PricesUsd = decimal.TryParse(scryfall.Prices?.Usd, out var usd) ? usd : null,
            PricesUsdFoil = decimal.TryParse(scryfall.Prices?.UsdFoil, out var foil) ? foil : null,
            Hash = null, // Must be computed separately
            // Denormalized Oracle fields
            ManaCost = scryfall.ManaCost,
            TypeLine = scryfall.TypeLine,
            OracleText = scryfall.OracleText,
            Power = scryfall.Power,
            Toughness = scryfall.Toughness,
        };
    }
    
    /// <summary>
    /// Converts a Scryfall set to a Set model.
    /// </summary>
    public static Set ToSet(this ScryfallSet scryfall)
    {
        return new Set
        {
            Id = scryfall.Id ?? Guid.NewGuid().ToString(),
            Code = scryfall.Code ?? "???",
            Name = scryfall.Name ?? "Unknown",
            SetType = scryfall.SetType,
            ReleasedAt = scryfall.ReleasedAt,
            CardCount = scryfall.CardCount,
            IconSvgUri = scryfall.IconSvgUri,
            Digital = scryfall.Digital,
            ParentSetCode = scryfall.ParentSetCode,
            Block = scryfall.Block,
        };
    }
    
    public static string? GetImageUri(this ScryfallCard card, string size = "normal")
    {
        var uris = card.ImageUris ?? card.CardFaces?.FirstOrDefault()?.ImageUris;
        
        return size.ToLowerInvariant() switch
        {
            "small" => uris?.Small,
            "large" => uris?.Large,
            "png" => uris?.Png,
            "art_crop" => uris?.ArtCrop,
            "border_crop" => uris?.BorderCrop,
            _ => uris?.Normal
        };
    }
}

#endregion
