using Lua.IO;
using System.Text;
using NUnit.Framework;

namespace Lua.Tests;

public class IOTests : IDisposable
{
    readonly string testDirectory;
    readonly FileSystem fileSystem;

    public IOTests()
    {
        testDirectory = Path.Combine(Path.GetTempPath(), $"LuaIOTests_{Guid.NewGuid()}");
        Directory.CreateDirectory(testDirectory);
        fileSystem = new();
    }

    public void Dispose()
    {
        if (Directory.Exists(testDirectory))
        {
            Directory.Delete(testDirectory, true);
        }
    }

    string GetTestFilePath(string filename)
    {
        return Path.Combine(testDirectory, filename);
    }

    [Test]
    public async Task TextStream_Write_And_Read_Text()
    {
        var testFile = GetTestFilePath("text_test.txt");
        var testContent = "Hello, World!\nThis is a test.";

        // Write text
        using (var stream = await fileSystem.Open(testFile, LuaFileOpenMode.Write, CancellationToken.None))
        {
            await stream.WriteAsync(testContent, CancellationToken.None);
        }

        // Read text
        using (var stream = await fileSystem.Open(testFile, LuaFileOpenMode.Read, CancellationToken.None))
        {
            var content = await stream.ReadAllAsync(CancellationToken.None);
            Assert.That(content, Is.EqualTo(testContent));
        }
    }


    [Test]
    public async Task TextStream_ReadLine_Works()
    {
        var testFile = GetTestFilePath("multiline.txt");
        var lines = new[] { "Line 1", "Line 2", "Line 3" };

        // Write multiple lines
        using (var stream = await fileSystem.Open(testFile, LuaFileOpenMode.Write, CancellationToken.None))
        {
            await stream.WriteAsync(string.Join("\n", lines), CancellationToken.None);
        }

        // Read lines one by one
        using (var stream = await fileSystem.Open(testFile, LuaFileOpenMode.Read, CancellationToken.None))
        {
            for (var i = 0; i < lines.Length; i++)
            {
                var line = await stream.ReadLineAsync(false, CancellationToken.None);
                Assert.That(line, Is.EqualTo(lines[i]));
            }

            // EOF should return null
            var eofLine = await stream.ReadLineAsync(false, CancellationToken.None);
            Assert.That(eofLine, Is.Null);
        }
    }

    [Test]
    public async Task TextStream_ReadString_Works()
    {
        var testFile = GetTestFilePath("read_string.txt");
        var testContent = "Hello, World!";

        // Write content
        using (var stream = await fileSystem.Open(testFile, LuaFileOpenMode.Write, CancellationToken.None))
        {
            await stream.WriteAsync(testContent, CancellationToken.None);
        }

        // Read partial strings
        using (var stream = await fileSystem.Open(testFile, LuaFileOpenMode.Read, CancellationToken.None))
        {
            var part1 = await stream.ReadAsync(5, CancellationToken.None);
            Assert.That(part1, Is.EqualTo("Hello"));

            var part2 = await stream.ReadAsync(7, CancellationToken.None);
            Assert.That(part2, Is.EqualTo(", World"));

            var part3 = await stream.ReadAsync(1, CancellationToken.None);
            Assert.That(part3, Is.EqualTo("!")); // Only 1 char left

            var eof = await stream.ReadAsync(10, CancellationToken.None);
            Assert.That(eof, Is.Null);
        }
    }


    [Test]
    public async Task Append_Mode_Appends_Content()
    {
        var testFile = GetTestFilePath("append_test.txt");

        // Write initial content
        using (var stream = await fileSystem.Open(testFile, LuaFileOpenMode.Write, CancellationToken.None))
        {
            await stream.WriteAsync("Hello", CancellationToken.None);
        }

        // Append content
        using (var stream = await fileSystem.Open(testFile, LuaFileOpenMode.Append, CancellationToken.None))
        {
            await stream.WriteAsync(" World", CancellationToken.None);
        }

        // Read and verify
        using (var stream = await fileSystem.Open(testFile, LuaFileOpenMode.Read, CancellationToken.None))
        {
            var content = await stream.ReadAllAsync(CancellationToken.None);
            Assert.That(content, Is.EqualTo("Hello World"));
        }
    }

    [Test]
    public async Task Seek_Works_Correctly()
    {
        var testFile = GetTestFilePath("seek_test.txt");
        var testContent = "0123456789";

        // Write content
        using (var stream = await fileSystem.Open(testFile, LuaFileOpenMode.Write, CancellationToken.None))
        {
            await stream.WriteAsync(testContent, CancellationToken.None);
        }

        // Test seeking
        using (var stream = await fileSystem.Open(testFile, LuaFileOpenMode.Read, CancellationToken.None))
        {
            // Seek from beginning
            stream.Seek(SeekOrigin.Begin, 5);
            var afterBegin = await stream.ReadAsync(3, CancellationToken.None);
            Assert.That(afterBegin, Is.EqualTo("567"));

            // Seek from current
            stream.Seek(SeekOrigin.Current, -2);
            var afterCurrent = await stream.ReadAsync(2, CancellationToken.None);
            Assert.That(afterCurrent, Is.EqualTo("67"));

            // Seek from end
            stream.Seek(SeekOrigin.End, -3);
            var afterEnd = await stream.ReadAsync(3, CancellationToken.None);
            Assert.That(afterEnd, Is.EqualTo("789"));
        }
    }

    [Test]
    public async Task FileSystem_Rename_Works()
    {
        var oldPath = GetTestFilePath("old_name.txt");
        var newPath = GetTestFilePath("new_name.txt");

        File.WriteAllText(oldPath, "test content");

        await fileSystem.Rename(oldPath, newPath, CancellationToken.None);

        Assert.That(File.Exists(oldPath), Is.False);
        Assert.That(File.Exists(newPath), Is.True);
        Assert.That(File.ReadAllText(newPath), Is.EqualTo("test content"));
    }

    [Test]
    public async Task FileSystem_Remove_Works()
    {
        var testFile = GetTestFilePath("remove_test.txt");

        File.WriteAllText(testFile, "test content");
        Assert.That(File.Exists(testFile), Is.True);

        await fileSystem.Remove(testFile, CancellationToken.None);

        Assert.That(File.Exists(testFile), Is.False);
    }

    [Test]
    public void FileSystem_IsReadable_Works()
    {
        var existingFile = GetTestFilePath("readable.txt");
        var nonExistentFile = GetTestFilePath("non_existent.txt");

        File.WriteAllText(existingFile, "test");

        Assert.That(fileSystem.IsReadable(existingFile), Is.True);
        Assert.That(fileSystem.IsReadable(nonExistentFile), Is.False);
    }

    [Test]
    public async Task FileSystem_TempFile_Works()
    {
        string? tempPath = null;

        try
        {
            using (var tempStream = await fileSystem.OpenTempFileStream(CancellationToken.None))
            {
                await tempStream.WriteAsync("temp content".AsMemory(), CancellationToken.None);

                // Seek and read
                tempStream.Seek(SeekOrigin.Begin, 0);
                var content = await tempStream.ReadAllAsync(CancellationToken.None);
                Assert.That(content, Is.EqualTo("temp content"));
            }
        }
        finally
        {
            if (tempPath != null && File.Exists(tempPath))
            {
                File.Delete(tempPath);
            }
        }
    }

    [Test]
    public void FileSystem_DirectorySeparator_IsValid()
    {
        var separator = fileSystem.DirectorySeparator;
        Assert.That(separator, Is.Not.Null);
        Assert.That(separator, Is.Not.Empty);
        Assert.That(separator, Is.EqualTo(Path.DirectorySeparatorChar.ToString()));
    }

    [Test]
    public async Task Buffering_Modes_Work()
    {
        var testFile = GetTestFilePath("buffer_test.txt");

        using (var stream = await fileSystem.Open(testFile, LuaFileOpenMode.Write, CancellationToken.None))
        {
            // Set no buffering
            stream.SetVBuf(LuaFileBufferingMode.NoBuffering, 0);
            await stream.WriteAsync("No buffer", CancellationToken.None);

            // Set line buffering
            stream.SetVBuf(LuaFileBufferingMode.LineBuffering, 1024);
            await stream.WriteAsync("\nLine buffer", CancellationToken.None);

            // Set full buffering
            stream.SetVBuf(LuaFileBufferingMode.FullBuffering, 4096);
            await stream.WriteAsync("\nFull buffer", CancellationToken.None);

            // Explicit flush
            await stream.FlushAsync(CancellationToken.None);
        }

        // Verify content was written
        var writtenContent = File.ReadAllText(testFile);
        Assert.That(writtenContent, Does.Contain("No buffer"));
        Assert.That(writtenContent, Does.Contain("Line buffer"));
        Assert.That(writtenContent, Does.Contain("Full buffer"));
    }

    [Test]
    public async Task LuaFileContent_Memory_Variations()
    {
        var testFile = GetTestFilePath("memory_test.txt");

        // Test with char array
        var charArray = "Hello from char array".ToCharArray();
        using (var stream = await fileSystem.Open(testFile, LuaFileOpenMode.Write, CancellationToken.None))
        {
            await stream.WriteAsync(charArray, CancellationToken.None);
        }

        using (var stream = await fileSystem.Open(testFile, LuaFileOpenMode.Read, CancellationToken.None))
        {
            var content = await stream.ReadAllAsync(CancellationToken.None);
            Assert.That(content, Is.EqualTo("Hello from char array"));
        }

        // Test with partial char array
        var longCharArray = "Hello World!!!".ToCharArray();
        using (var stream = await fileSystem.Open(testFile, LuaFileOpenMode.Write, CancellationToken.None))
        {
            await stream.WriteAsync(longCharArray.AsMemory(0, 11), CancellationToken.None); // Only "Hello World"
        }

        using (var stream = await fileSystem.Open(testFile, LuaFileOpenMode.Read, CancellationToken.None))
        {
            var content = await stream.ReadAllAsync(CancellationToken.None);
            Assert.That(content, Is.EqualTo("Hello World"));
        }
    }
}