using Microsoft.Data.Sqlite;
using Scry.Core.Data;
using Scry.Core.Models;
using Xunit;

namespace Scry.Tests;

public class CardDatabaseTests : IDisposable
{
    private readonly string _dbPath;
    private readonly CardDatabase _database;
    
    public CardDatabaseTests()
    {
        _dbPath = Path.Combine(Path.GetTempPath(), $"scry_test_{Guid.NewGuid()}.db");
        _database = new CardDatabase(_dbPath);
    }
    
    [Fact]
    public async Task InsertCard_ThenRetrieve_ReturnsMatch()
    {
        // First insert oracle and set (foreign keys)
        var oracle = new Oracle
        {
            Id = "oracle-1",
            Name = "Test Card",
            ManaCost = "{1}{U}",
            TypeLine = "Creature"
        };
        await _database.InsertOracleAsync(oracle);
        
        var set = new Set
        {
            Id = "set-1",
            Code = "TST",
            Name = "Test Set"
        };
        await _database.InsertSetAsync(set);
        
        var card = new Card
        {
            Id = "test-id",
            OracleId = "oracle-1",
            SetId = "set-1",
            SetCode = "TST",
            Name = "Test Card",
            CollectorNumber = "1",
            Hash = new byte[] { 0x01, 0x02, 0x03 },
            ImageUri = "https://example.com/image.jpg"
        };
        
        await _database.InsertCardAsync(card);
        var retrieved = await _database.GetCardByIdAsync("test-id");
        
        Assert.NotNull(retrieved);
        Assert.Equal("Test Card", retrieved.Name);
        Assert.Equal("TST", retrieved.SetCode);
        Assert.Equal(card.Hash, retrieved.Hash);
    }
    
    [Fact]
    public async Task InsertCardBatch_InsertsAllCards()
    {
        // Insert oracle first
        var oracle = new Oracle { Id = "oracle-batch", Name = "Batch Card" };
        await _database.InsertOracleAsync(oracle);
        
        var set = new Set { Id = "set-batch", Code = "TST", Name = "Test Set" };
        await _database.InsertSetAsync(set);
        
        var cards = Enumerable.Range(0, 100).Select(i => new Card
        {
            Id = $"card-{i}",
            OracleId = "oracle-batch",
            SetId = "set-batch",
            SetCode = "TST",
            Name = $"Card {i}",
            Hash = new byte[] { (byte)i }
        }).ToList();
        
        await _database.InsertCardBatchAsync(cards);
        var count = await _database.GetCardCountAsync();
        
        Assert.Equal(100, count);
    }
    
    [Fact]
    public async Task GetAllCards_ReturnsAllCards()
    {
        var oracle = new Oracle { Id = "oracle-all", Name = "All Card" };
        await _database.InsertOracleAsync(oracle);
        
        var set = new Set { Id = "set-all", Code = "TST", Name = "Test Set" };
        await _database.InsertSetAsync(set);
        
        var cards = Enumerable.Range(0, 10).Select(i => new Card
        {
            Id = $"card-{i}",
            OracleId = "oracle-all",
            SetId = "set-all",
            SetCode = "TST",
            Name = $"Card {i}",
            Hash = new byte[] { (byte)i }
        }).ToList();
        
        await _database.InsertCardBatchAsync(cards);
        var all = await _database.GetAllCardsAsync();
        
        Assert.Equal(10, all.Count);
    }
    
    [Fact]
    public async Task GetCardsByOracleId_ReturnsAllPrintings()
    {
        var oracle = new Oracle { Id = "oracle-multi", Name = "Multi Print Card" };
        await _database.InsertOracleAsync(oracle);
        
        var set1 = new Set { Id = "set-1", Code = "S1", Name = "Set 1" };
        var set2 = new Set { Id = "set-2", Code = "S2", Name = "Set 2" };
        await _database.InsertSetAsync(set1);
        await _database.InsertSetAsync(set2);
        
        var cards = new[]
        {
            new Card { Id = "print-1", OracleId = "oracle-multi", SetId = "set-1", SetCode = "S1", Name = "Multi Print Card", Hash = new byte[] { 0x01 } },
            new Card { Id = "print-2", OracleId = "oracle-multi", SetId = "set-2", SetCode = "S2", Name = "Multi Print Card", Hash = new byte[] { 0x02 } },
        };
        
        await _database.InsertCardBatchAsync(cards);
        var printings = await _database.GetCardsByOracleIdAsync("oracle-multi");
        
        Assert.Equal(2, printings.Count);
    }
    
    [Fact]
    public async Task Metadata_SetAndGet()
    {
        await _database.SetMetadataAsync("test_key", "test_value");
        var value = await _database.GetMetadataAsync("test_key");
        
        Assert.Equal("test_value", value);
    }
    
    [Fact]
    public async Task ClearCards_RemovesAllCards()
    {
        var oracle = new Oracle { Id = "oracle-clear", Name = "Clear Card" };
        await _database.InsertOracleAsync(oracle);
        
        var set = new Set { Id = "set-clear", Code = "TST", Name = "Test Set" };
        await _database.InsertSetAsync(set);
        
        var cards = Enumerable.Range(0, 10).Select(i => new Card
        {
            Id = $"card-{i}",
            OracleId = "oracle-clear",
            SetId = "set-clear",
            SetCode = "TST",
            Name = $"Card {i}",
            Hash = new byte[] { (byte)i }
        }).ToList();
        
        await _database.InsertCardBatchAsync(cards);
        await _database.ClearCardsAsync();
        var count = await _database.GetCardCountAsync();
        
        Assert.Equal(0, count);
    }
    
    [Fact]
    public async Task InsertCard_DuplicateId_Updates()
    {
        var oracle = new Oracle { Id = "oracle-dup", Name = "Dup Card" };
        await _database.InsertOracleAsync(oracle);
        
        var set = new Set { Id = "set-dup", Code = "TST", Name = "Test Set" };
        await _database.InsertSetAsync(set);
        
        var card1 = new Card
        {
            Id = "duplicate-id",
            OracleId = "oracle-dup",
            SetId = "set-dup",
            SetCode = "TST",
            Name = "Original Name",
            Hash = new byte[] { 0x01 }
        };
        
        var card2 = new Card
        {
            Id = "duplicate-id",
            OracleId = "oracle-dup",
            SetId = "set-dup",
            SetCode = "TST",
            Name = "Updated Name",
            Hash = new byte[] { 0x02 }
        };
        
        await _database.InsertCardAsync(card1);
        await _database.InsertCardAsync(card2);
        
        var retrieved = await _database.GetCardByIdAsync("duplicate-id");
        
        Assert.NotNull(retrieved);
        Assert.Equal("Updated Name", retrieved.Name);
        Assert.Equal(new byte[] { 0x02 }, retrieved.Hash);
    }
    
    [Fact]
    public async Task InsertOracle_ThenRetrieveByName()
    {
        var oracle = new Oracle
        {
            Id = "oracle-name",
            Name = "Lightning Bolt",
            ManaCost = "{R}",
            Cmc = 1,
            TypeLine = "Instant",
            OracleText = "Lightning Bolt deals 3 damage to any target."
        };
        
        await _database.InsertOracleAsync(oracle);
        var retrieved = await _database.GetOracleByNameAsync("Lightning Bolt");
        
        Assert.NotNull(retrieved);
        Assert.Equal("{R}", retrieved.ManaCost);
        Assert.Equal(1, retrieved.Cmc);
    }
    
    [Fact]
    public async Task InsertSet_ThenRetrieveByCode()
    {
        var set = new Set
        {
            Id = "set-lea",
            Code = "lea",
            Name = "Limited Edition Alpha",
            SetType = "expansion",
            ReleasedAt = "1993-08-05",
            CardCount = 295
        };
        
        await _database.InsertSetAsync(set);
        var retrieved = await _database.GetSetByCodeAsync("lea");
        
        Assert.NotNull(retrieved);
        Assert.Equal("Limited Edition Alpha", retrieved.Name);
        Assert.Equal(295, retrieved.CardCount);
    }
    
    [Fact]
    public async Task GetCardsWithHash_OnlyReturnsCardsWithHash()
    {
        var oracle = new Oracle { Id = "oracle-hash", Name = "Hash Card" };
        await _database.InsertOracleAsync(oracle);
        
        var set = new Set { Id = "set-hash", Code = "TST", Name = "Test Set" };
        await _database.InsertSetAsync(set);
        
        var cardWithHash = new Card
        {
            Id = "card-with-hash",
            OracleId = "oracle-hash",
            SetId = "set-hash",
            SetCode = "TST",
            Name = "Has Hash",
            Hash = new byte[] { 0x01 }
        };
        
        var cardWithoutHash = new Card
        {
            Id = "card-no-hash",
            OracleId = "oracle-hash",
            SetId = "set-hash",
            SetCode = "TST",
            Name = "No Hash",
            Hash = null
        };
        
        await _database.InsertCardAsync(cardWithHash);
        await _database.InsertCardAsync(cardWithoutHash);
        
        var cardsWithHash = await _database.GetCardsWithHashAsync();
        
        Assert.Single(cardsWithHash);
        Assert.Equal("card-with-hash", cardsWithHash[0].Id);
    }
    
    public void Dispose()
    {
        _database.Dispose();
        SqliteConnection.ClearAllPools();
        try
        {
            if (File.Exists(_dbPath))
            {
                File.Delete(_dbPath);
            }
        }
        catch (IOException)
        {
        }
    }
}
