using Scry.Core.Data;
using Scry.Core.Imaging;
using Scry.Core.Models;
using Scry.Core.Scryfall;
using SkiaSharp;

namespace Scry.Core.Recognition;

public class HashDatabaseSyncService
{
    private readonly ScryfallClient _scryfallClient;
    private readonly CardDatabase _database;
    private readonly HttpClient _imageClient;
    
    public event Action<SyncProgress>? OnProgress;
    
    public HashDatabaseSyncService(ScryfallClient scryfallClient, CardDatabase database, HttpClient? imageClient = null)
    {
        _scryfallClient = scryfallClient;
        _database = database;
        _imageClient = imageClient ?? new HttpClient();
    }
    
    public async Task<SyncResult> SyncAsync(SyncOptions? options = null, CancellationToken ct = default)
    {
        options ??= new SyncOptions();
        var result = new SyncResult();
        var startTime = DateTime.UtcNow;
        
        try
        {
            // Fetch all sets first
            ReportProgress(new SyncProgress { Stage = SyncStage.Initializing, Message = "Fetching sets..." });
            var scryfallSets = await _scryfallClient.GetAllSetsAsync(ct);
            var existingSetIds = await _database.GetExistingSetIdsAsync(ct);
            
            var newSets = scryfallSets
                .Where(s => s.Id != null && !existingSetIds.Contains(s.Id))
                .Select(s => s.ToSet())
                .ToList();
            
            if (newSets.Count > 0)
            {
                await _database.InsertSetBatchAsync(newSets, ct);
            }
            
            var bulkInfo = await _scryfallClient.GetBulkDataInfoAsync(options.BulkDataType, ct);
            if (bulkInfo?.DownloadUri == null)
            {
                result.Error = "Could not get bulk data info from Scryfall";
                return result;
            }
            
            var lastSync = await _database.GetMetadataAsync("last_sync_date", ct);
            if (!options.ForceFullSync && lastSync != null)
            {
                if (DateTime.TryParse(lastSync, out var lastSyncDate) && 
                    bulkInfo.UpdatedAt <= lastSyncDate)
                {
                    result.Skipped = true;
                    result.Message = "Database is up to date";
                    return result;
                }
            }
            
            ReportProgress(new SyncProgress { Stage = SyncStage.Downloading, Message = "Downloading card data..." });
            
            var existingOracleIds = await _database.GetExistingOracleIdsAsync(ct);
            var cardBatch = new List<Card>();
            var oracleBatch = new Dictionary<string, Oracle>();
            var processed = 0;
            var errors = 0;
            
            await foreach (var scryfallCard in _scryfallClient.StreamBulkDataAsync(bulkInfo.DownloadUri, ct))
            {
                ct.ThrowIfCancellationRequested();
                
                if (scryfallCard.Lang != "en" && !options.IncludeNonEnglish)
                    continue;
                
                var imageUri = scryfallCard.GetImageUri(options.ImageSize);
                if (string.IsNullOrEmpty(imageUri))
                    continue;
                
                var oracleId = scryfallCard.OracleId ?? scryfallCard.Id ?? "";
                
                try
                {
                    var imageBytes = await DownloadWithRetryAsync(imageUri, options.MaxRetries, ct);
                    if (imageBytes == null)
                    {
                        errors++;
                        continue;
                    }
                    
                    using var bitmap = SKBitmap.Decode(imageBytes);
                    if (bitmap == null)
                    {
                        errors++;
                        continue;
                    }
                    
                    using var preprocessed = ImagePreprocessor.ApplyClahe(bitmap);
                    var hash = PerceptualHash.ComputeColorHash(preprocessed);
                    
                    // Track oracle if new
                    if (!existingOracleIds.Contains(oracleId) && !oracleBatch.ContainsKey(oracleId))
                    {
                        oracleBatch[oracleId] = scryfallCard.ToOracle();
                    }
                    
                    // Create card with hash
                    var card = scryfallCard.ToCard() with { Hash = hash };
                    cardBatch.Add(card);
                    
                    processed++;
                    
                    if (cardBatch.Count >= options.BatchSize)
                    {
                        // Insert oracles first
                        if (oracleBatch.Count > 0)
                        {
                            await _database.InsertOracleBatchAsync(oracleBatch.Values, ct);
                            foreach (var id in oracleBatch.Keys)
                            {
                                existingOracleIds.Add(id);
                            }
                            oracleBatch.Clear();
                        }
                        
                        await _database.InsertCardBatchAsync(cardBatch, ct);
                        result.ProcessedCards += cardBatch.Count;
                        cardBatch.Clear();
                        
                        ReportProgress(new SyncProgress
                        {
                            Stage = SyncStage.Processing,
                            ProcessedCards = result.ProcessedCards,
                            Message = $"Processed {result.ProcessedCards} cards..."
                        });
                    }
                    
                    if (options.RateLimitMs > 0)
                    {
                        await Task.Delay(options.RateLimitMs, ct);
                    }
                }
                catch (Exception ex)
                {
                    errors++;
                    if (options.StopOnError)
                        throw;
                    
                    result.Errors.Add($"{scryfallCard.Name}: {ex.Message}");
                }
            }
            
            // Insert remaining batches
            if (oracleBatch.Count > 0)
            {
                await _database.InsertOracleBatchAsync(oracleBatch.Values, ct);
            }
            
            if (cardBatch.Count > 0)
            {
                await _database.InsertCardBatchAsync(cardBatch, ct);
                result.ProcessedCards += cardBatch.Count;
            }
            
            await _database.SetMetadataAsync("last_sync_date", DateTime.UtcNow.ToString("O"), ct);
            await _database.SetMetadataAsync("bulk_data_updated", bulkInfo.UpdatedAt?.ToString("O") ?? "", ct);
            
            result.Success = true;
            result.Duration = DateTime.UtcNow - startTime;
            result.ErrorCount = errors;
            result.Message = $"Synced {result.ProcessedCards} cards in {result.Duration.TotalSeconds:F1}s";
            
            ReportProgress(new SyncProgress
            {
                Stage = SyncStage.Complete,
                ProcessedCards = result.ProcessedCards,
                Message = result.Message
            });
        }
        catch (OperationCanceledException)
        {
            result.Cancelled = true;
            result.Message = "Sync cancelled";
        }
        catch (Exception ex)
        {
            result.Error = ex.Message;
        }
        
        return result;
    }
    
    private async Task<byte[]?> DownloadWithRetryAsync(string url, int maxRetries, CancellationToken ct)
    {
        for (var attempt = 0; attempt <= maxRetries; attempt++)
        {
            try
            {
                return await _imageClient.GetByteArrayAsync(url, ct);
            }
            catch (HttpRequestException) when (attempt < maxRetries)
            {
                await Task.Delay(1000 * (attempt + 1), ct);
            }
        }
        
        return null;
    }
    
    private void ReportProgress(SyncProgress progress)
    {
        OnProgress?.Invoke(progress);
    }
}

public class SyncOptions
{
    public string BulkDataType { get; set; } = "unique_artwork";
    public string ImageSize { get; set; } = "normal";
    public int BatchSize { get; set; } = 100;
    public int RateLimitMs { get; set; } = 50;
    public int MaxRetries { get; set; } = 3;
    public bool ForceFullSync { get; set; }
    public bool IncludeNonEnglish { get; set; }
    public bool StopOnError { get; set; }
}

public class SyncResult
{
    public bool Success { get; set; }
    public bool Skipped { get; set; }
    public bool Cancelled { get; set; }
    public int ProcessedCards { get; set; }
    public int ErrorCount { get; set; }
    public TimeSpan Duration { get; set; }
    public string? Message { get; set; }
    public string? Error { get; set; }
    public List<string> Errors { get; set; } = new();
}

public class SyncProgress
{
    public SyncStage Stage { get; set; }
    public int ProcessedCards { get; set; }
    public int TotalCards { get; set; }
    public string? Message { get; set; }
    
    public float Percentage => TotalCards > 0 ? (float)ProcessedCards / TotalCards * 100 : 0;
}

public enum SyncStage
{
    Initializing,
    Downloading,
    Processing,
    Complete,
    Error
}
