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

namespace Scry.Tests;

public class PerceptualHashTests
{
    [Fact]
    public void ComputeHash_ReturnsConsistentHash()
    {
        using var bitmap = CreateTestBitmap(32, 32, SKColors.Red);
        
        var hash1 = PerceptualHash.ComputeHash(bitmap);
        var hash2 = PerceptualHash.ComputeHash(bitmap);
        
        Assert.Equal(hash1, hash2);
    }
    
    [Fact]
    public void ComputeColorHash_Returns24Bytes()
    {
        using var bitmap = CreateTestBitmap(32, 32, SKColors.Blue);
        
        var hash = PerceptualHash.ComputeColorHash(bitmap);
        
        Assert.Equal(24, hash.Length);
    }
    
    [Fact]
    public void HammingDistance_IdenticalHashes_ReturnsZero()
    {
        var hash = new byte[] { 0xFF, 0x00, 0xAB, 0xCD };
        
        var distance = PerceptualHash.HammingDistance(hash, hash);
        
        Assert.Equal(0, distance);
    }
    
    [Fact]
    public void HammingDistance_OppositeHashes_ReturnsMaxBits()
    {
        var hash1 = new byte[] { 0x00, 0x00 };
        var hash2 = new byte[] { 0xFF, 0xFF };
        
        var distance = PerceptualHash.HammingDistance(hash1, hash2);
        
        Assert.Equal(16, distance);
    }
    
    [Fact]
    public void HammingDistance_SingleBitDifference()
    {
        var hash1 = new byte[] { 0b00000000 };
        var hash2 = new byte[] { 0b00000001 };
        
        var distance = PerceptualHash.HammingDistance(hash1, hash2);
        
        Assert.Equal(1, distance);
    }
    
    [Fact]
    public void CalculateConfidence_ZeroDistance_ReturnsOne()
    {
        var confidence = PerceptualHash.CalculateConfidence(0, 192);
        
        Assert.Equal(1.0f, confidence);
    }
    
    [Fact]
    public void CalculateConfidence_HalfDistance_ReturnsHalf()
    {
        var confidence = PerceptualHash.CalculateConfidence(96, 192);
        
        Assert.Equal(0.5f, confidence);
    }
    
    [Theory]
    [InlineData("reference/brainstorm.png")]
    [InlineData("reference/force_of_will.png")]
    [InlineData("single_cards/llanowar_elves.jpg")]
    public void ComputeColorHash_RealImages_ProducesValidHash(string imagePath)
    {
        var fullPath = Path.Combine("TestImages", imagePath);
        if (!File.Exists(fullPath))
        {
            return;
        }
        
        using var bitmap = SKBitmap.Decode(fullPath);
        Assert.NotNull(bitmap);
        
        var hash = PerceptualHash.ComputeColorHash(bitmap);
        
        Assert.Equal(24, hash.Length);
        Assert.True(hash.Any(b => b != 0), "Hash should not be all zeros");
    }
    
    [Fact]
    public void SimilarImages_HaveLowHammingDistance()
    {
        using var bitmap1 = CreateGradientBitmap(32, 32, SKColors.Red, SKColors.Blue);
        using var bitmap2 = CreateGradientBitmap(32, 32, SKColors.Red, SKColors.Blue);
        
        var hash1 = PerceptualHash.ComputeColorHash(bitmap1);
        var hash2 = PerceptualHash.ComputeColorHash(bitmap2);
        
        var distance = PerceptualHash.HammingDistance(hash1, hash2);
        
        Assert.Equal(0, distance);
    }
    
    [Fact]
    public void DifferentImages_HaveHighHammingDistance()
    {
        using var bitmap1 = CreateTestBitmap(32, 32, SKColors.Red);
        using var bitmap2 = CreateTestBitmap(32, 32, SKColors.Blue);
        
        var hash1 = PerceptualHash.ComputeColorHash(bitmap1);
        var hash2 = PerceptualHash.ComputeColorHash(bitmap2);
        
        var distance = PerceptualHash.HammingDistance(hash1, hash2);
        
        Assert.True(distance > 10, $"Expected distance > 10, got {distance}");
    }
    
    private static SKBitmap CreateTestBitmap(int width, int height, SKColor color)
    {
        var bitmap = new SKBitmap(width, height, SKColorType.Rgba8888, SKAlphaType.Premul);
        using var canvas = new SKCanvas(bitmap);
        canvas.Clear(color);
        return bitmap;
    }
    
    private static SKBitmap CreateGradientBitmap(int width, int height, SKColor start, SKColor end)
    {
        var bitmap = new SKBitmap(width, height, SKColorType.Rgba8888, SKAlphaType.Premul);
        using var canvas = new SKCanvas(bitmap);
        using var paint = new SKPaint();
        paint.Shader = SKShader.CreateLinearGradient(
            new SKPoint(0, 0),
            new SKPoint(width, height),
            new[] { start, end },
            SKShaderTileMode.Clamp);
        canvas.DrawRect(0, 0, width, height, paint);
        return bitmap;
    }
}
