using ClaudeDo.Data;

namespace ClaudeDo.Data.Tests;

public sealed class AttachmentStoreTests : IDisposable
{
    private readonly string _root;
    private readonly AttachmentStore _store;

    public AttachmentStoreTests()
    {
        _root = Path.Combine(Path.GetTempPath(), $"claudedo_att_{Guid.NewGuid():N}");
        Directory.CreateDirectory(_root);
        _store = new AttachmentStore(_root);
    }

    public void Dispose()
    {
        try { Directory.Delete(_root, recursive: true); } catch { }
    }

    [Fact]
    public async Task Save_then_readback_size_matches()
    {
        var bytes = new byte[1024];
        new Random(42).NextBytes(bytes);
        using var ms = new MemoryStream(bytes);

        var written = await _store.SaveAsync("task1", "data.bin", ms);

        Assert.Equal(1024, written);
        var filePath = Path.Combine(_store.TaskDir("task1"), "data.bin");
        Assert.Equal(1024, new FileInfo(filePath).Length);
    }

    [Fact]
    public async Task Rejects_fileName_with_dotdot()
    {
        using var ms = new MemoryStream(new byte[10]);
        await Assert.ThrowsAsync<ArgumentException>(() =>
            _store.SaveAsync("task1", "../escape.txt", ms));
    }

    [Fact]
    public async Task Rejects_fileName_with_directory_separator()
    {
        using var ms = new MemoryStream(new byte[10]);
        await Assert.ThrowsAsync<ArgumentException>(() =>
            _store.SaveAsync("task1", "sub/file.txt", ms));
    }

    [Fact]
    public async Task Rejects_content_over_5MB()
    {
        var over = new byte[5 * 1024 * 1024 + 1];
        using var ms = new MemoryStream(over);
        await Assert.ThrowsAsync<InvalidOperationException>(() =>
            _store.SaveAsync("task1", "big.bin", ms));
    }

    [Fact]
    public void DeleteTaskDir_removes_directory()
    {
        var dir = _store.TaskDir("task2");
        Directory.CreateDirectory(dir);
        File.WriteAllText(Path.Combine(dir, "a.txt"), "x");

        _store.DeleteTaskDir("task2");

        Assert.False(Directory.Exists(dir));
    }

    [Fact]
    public void Deleting_missing_file_is_noop()
    {
        // Should not throw
        _store.DeleteFile("taskX", "nonexistent.txt");
    }

    [Fact]
    public void DeleteFile_removes_file_and_ignores_missing_second_call()
    {
        var dir = _store.TaskDir("task3");
        Directory.CreateDirectory(dir);
        var filePath = Path.Combine(dir, "remove.txt");
        File.WriteAllText(filePath, "data");

        _store.DeleteFile("task3", "remove.txt");
        Assert.False(File.Exists(filePath));

        // Second call must not throw
        _store.DeleteFile("task3", "remove.txt");
    }
}
