using Scry.Core.Imaging;
using SkiaSharp;
using Xunit;
using Xunit.Abstractions;

namespace Scry.Tests;

public class TestImageBenchmarks
{
    private readonly ITestOutputHelper _output;
    private static readonly string TestImagesDir = "TestImages";
    
    public TestImageBenchmarks(ITestOutputHelper output)
    {
        _output = output;
    }
    
    [Fact]
    public void ProcessAllTestImages_ComputeHashes()
    {
        if (!Directory.Exists(TestImagesDir))
        {
            _output.WriteLine("TestImages directory not found, skipping benchmark");
            return;
        }
        
        var categories = Directory.GetDirectories(TestImagesDir);
        var results = new List<(string Category, int Count, double AvgTimeMs, int Failures)>();
        
        foreach (var categoryPath in categories)
        {
            var category = Path.GetFileName(categoryPath);
            var imageFiles = GetImageFiles(categoryPath);
            
            if (!imageFiles.Any())
                continue;
            
            var times = new List<double>();
            var failures = 0;
            
            foreach (var file in imageFiles)
            {
                try
                {
                    var sw = System.Diagnostics.Stopwatch.StartNew();
                    
                    using var bitmap = SKBitmap.Decode(file);
                    if (bitmap == null)
                    {
                        failures++;
                        continue;
                    }
                    
                    using var preprocessed = ImagePreprocessor.ApplyClahe(bitmap);
                    var hash = PerceptualHash.ComputeColorHash(preprocessed);
                    
                    sw.Stop();
                    times.Add(sw.Elapsed.TotalMilliseconds);
                }
                catch (Exception ex)
                {
                    failures++;
                    _output.WriteLine($"  Failed: {Path.GetFileName(file)} - {ex.Message}");
                }
            }
            
            if (times.Any())
            {
                var avgTime = times.Average();
                results.Add((category, times.Count, avgTime, failures));
                _output.WriteLine($"{category}: {times.Count} images, {avgTime:F1}ms avg, {failures} failures");
            }
        }
        
        _output.WriteLine("");
        _output.WriteLine("=== Summary ===");
        var totalImages = results.Sum(r => r.Count);
        var totalFailures = results.Sum(r => r.Failures);
        var overallAvg = results.SelectMany((r, _) => Enumerable.Repeat(r.AvgTimeMs, r.Count)).Average();
        
        _output.WriteLine($"Total: {totalImages} images processed");
        _output.WriteLine($"Failures: {totalFailures}");
        _output.WriteLine($"Overall avg: {overallAvg:F1}ms per image");
        
        Assert.True(totalImages > 0, "Should process at least some images");
    }
    
    [Theory]
    [InlineData("foil")]
    [InlineData("worn")]
    [InlineData("low_light")]
    [InlineData("foreign")]
    [InlineData("tokens")]
    public void ProcessCategory_AllImagesHash(string category)
    {
        var categoryPath = Path.Combine(TestImagesDir, category);
        if (!Directory.Exists(categoryPath))
        {
            _output.WriteLine($"Category not found: {category}");
            return;
        }
        
        var imageFiles = GetImageFiles(categoryPath);
        _output.WriteLine($"Processing {imageFiles.Count} images in {category}/");
        
        var processed = 0;
        var failed = 0;
        
        foreach (var file in imageFiles)
        {
            var fileName = Path.GetFileName(file);
            try
            {
                using var bitmap = SKBitmap.Decode(file);
                if (bitmap == null)
                {
                    _output.WriteLine($"  [DECODE FAIL] {fileName}");
                    failed++;
                    continue;
                }
                
                using var preprocessed = ImagePreprocessor.ApplyClahe(bitmap);
                var hash = PerceptualHash.ComputeColorHash(preprocessed);
                
                Assert.Equal(24, hash.Length);
                processed++;
                _output.WriteLine($"  [OK] {fileName} ({bitmap.Width}x{bitmap.Height})");
            }
            catch (Exception ex)
            {
                _output.WriteLine($"  [ERROR] {fileName}: {ex.Message}");
                failed++;
            }
        }
        
        _output.WriteLine($"");
        _output.WriteLine($"Results: {processed} OK, {failed} failed");
        
        Assert.True(processed > 0 || !imageFiles.Any(), $"Should process at least one image in {category}");
    }
    
    [Fact]
    public void HashStability_SameImageProducesSameHash()
    {
        var testFile = Path.Combine(TestImagesDir, "reference", "brainstorm.png");
        if (!File.Exists(testFile))
        {
            testFile = GetImageFiles(TestImagesDir).FirstOrDefault();
            if (testFile == null)
            {
                _output.WriteLine("No test images found");
                return;
            }
        }
        
        using var bitmap = SKBitmap.Decode(testFile);
        Assert.NotNull(bitmap);
        
        var hashes = new List<byte[]>();
        for (var i = 0; i < 5; i++)
        {
            using var preprocessed = ImagePreprocessor.ApplyClahe(bitmap);
            hashes.Add(PerceptualHash.ComputeColorHash(preprocessed));
        }
        
        for (var i = 1; i < hashes.Count; i++)
        {
            Assert.Equal(hashes[0], hashes[i]);
        }
        
        _output.WriteLine($"Hash is stable across {hashes.Count} runs");
    }
    
    [Fact]
    public void HashVariance_DifferentImagesProduceDifferentHashes()
    {
        var imageFiles = GetImageFiles(TestImagesDir).Take(20).ToList();
        if (imageFiles.Count < 2)
        {
            _output.WriteLine("Not enough test images for variance test");
            return;
        }
        
        var hashDict = new Dictionary<string, byte[]>();
        
        foreach (var file in imageFiles)
        {
            try
            {
                using var bitmap = SKBitmap.Decode(file);
                if (bitmap == null) continue;
                
                using var preprocessed = ImagePreprocessor.ApplyClahe(bitmap);
                var hash = PerceptualHash.ComputeColorHash(preprocessed);
                hashDict[file] = hash;
            }
            catch
            {
            }
        }
        
        var collisions = 0;
        var comparisons = 0;
        var files = hashDict.Keys.ToList();
        
        for (var i = 0; i < files.Count; i++)
        {
            for (var j = i + 1; j < files.Count; j++)
            {
                var distance = PerceptualHash.HammingDistance(hashDict[files[i]], hashDict[files[j]]);
                comparisons++;
                
                if (distance < 5)
                {
                    collisions++;
                    _output.WriteLine($"Near collision (distance={distance}): {Path.GetFileName(files[i])} vs {Path.GetFileName(files[j])}");
                }
            }
        }
        
        _output.WriteLine($"Checked {comparisons} pairs, found {collisions} near-collisions");
    }
    
    private static List<string> GetImageFiles(string directory)
    {
        var extensions = new[] { ".jpg", ".jpeg", ".png", ".webp", ".bmp" };
        
        return Directory.EnumerateFiles(directory, "*.*", SearchOption.AllDirectories)
            .Where(f => extensions.Contains(Path.GetExtension(f).ToLowerInvariant()))
            .ToList();
    }
}
