using LibHac.Common;
using LibHac.Fs;
using LibHac.Fs.Fsa;
using Xunit;

namespace LibHac.Tests.Fs.IFileSystemTestBase
{
    public abstract partial class CommittableIFileSystemTests
    {
        [Fact]
        public void Commit_AfterSuccessfulCommit_CanReadCommittedData()
        {
            // "Random" test data
            var data1 = new byte[] { 7, 4, 1, 0, 8, 5, 2, 9, 6, 3 };
            var data2 = new byte[] { 6, 1, 6, 8, 0, 3, 9, 7, 5, 1 };

            IReopenableFileSystemCreator fsCreator = GetFileSystemCreator();
            IFileSystem fs = fsCreator.Create();

            // Make sure to test both directories and files
            fs.CreateDirectory("/dir1".ToU8Span()).ThrowIfFailure();
            fs.CreateDirectory("/dir2".ToU8Span()).ThrowIfFailure();

            fs.CreateFile("/dir1/file".ToU8Span(), data1.Length, CreateFileOptions.None).ThrowIfFailure();
            fs.CreateFile("/dir2/file".ToU8Span(), data2.Length, CreateFileOptions.None).ThrowIfFailure();

            fs.OpenFile(out IFile file1, "/dir1/file".ToU8Span(), OpenMode.Write).ThrowIfFailure();
            fs.OpenFile(out IFile file2, "/dir2/file".ToU8Span(), OpenMode.Write).ThrowIfFailure();

            file1.Write(0, data1, WriteOption.Flush).ThrowIfFailure();
            file2.Write(0, data2, WriteOption.Flush).ThrowIfFailure();

            file1.Dispose();
            file2.Dispose();

            fs.Commit().ThrowIfFailure();
            fs.Dispose();

            // Reopen after committing
            fs = fsCreator.Create();

            var readData1 = new byte[data1.Length];
            var readData2 = new byte[data2.Length];

            Assert.Success(fs.OpenFile(out file1, "/dir1/file".ToU8Span(), OpenMode.Read));

            using (file1)
            {
                Assert.Success(file1.Read(out long bytesRead, 0, readData1, ReadOption.None));
                Assert.Equal(data1.Length, bytesRead);
            }

            Assert.Equal(data1, readData1);

            Assert.Success(fs.OpenFile(out file2, "/dir2/file".ToU8Span(), OpenMode.Read));

            using (file2)
            {
                Assert.Success(file2.Read(out long bytesRead, 0, readData2, ReadOption.None));
                Assert.Equal(data2.Length, bytesRead);
            }

            Assert.Equal(data2, readData2);
        }

        [Fact]
        public void Rollback_CreateFileThenRollback_FileDoesNotExist()
        {
            IFileSystem fs = CreateFileSystem();

            fs.CreateDirectory("/dir".ToU8Span()).ThrowIfFailure();
            fs.CreateFile("/dir/file".ToU8Span(), 0, CreateFileOptions.None).ThrowIfFailure();

            // Rollback should succeed
            Assert.Success(fs.Rollback());

            // Make sure the file and directory are gone
            Assert.Result(ResultFs.PathNotFound, fs.GetEntryType(out _, "/dir".ToU8Span()));
            Assert.Result(ResultFs.PathNotFound, fs.GetEntryType(out _, "/dir/file".ToU8Span()));
        }

        [Fact]
        public void Rollback_CreateFileThenCloseFs_FileDoesNotExist()
        {
            IReopenableFileSystemCreator fsCreator = GetFileSystemCreator();
            IFileSystem fs = fsCreator.Create();

            fs.CreateDirectory("/dir".ToU8Span()).ThrowIfFailure();
            fs.CreateFile("/dir/file".ToU8Span(), 0, CreateFileOptions.None).ThrowIfFailure();

            // Close without committing and reopen the file system
            fs.Dispose();
            fs = fsCreator.Create();

            // Make sure the file and directory are gone
            Assert.Result(ResultFs.PathNotFound, fs.GetEntryType(out _, "/dir".ToU8Span()));
            Assert.Result(ResultFs.PathNotFound, fs.GetEntryType(out _, "/dir/file".ToU8Span()));
        }

        [Fact]
        public void Rollback_AfterChangingExistingFiles_GoesBackToOriginalData()
        {
            // "Random" test data
            var data1 = new byte[] { 7, 4, 1, 0, 8, 5, 2, 9, 6, 3 };
            var data2 = new byte[] { 6, 1, 6, 8, 0, 3, 9, 7, 5, 1 };

            IReopenableFileSystemCreator fsCreator = GetFileSystemCreator();
            IFileSystem fs = fsCreator.Create();

            fs.CreateDirectory("/dir".ToU8Span()).ThrowIfFailure();
            fs.CreateFile("/dir/file".ToU8Span(), data1.Length, CreateFileOptions.None).ThrowIfFailure();

            fs.OpenFile(out IFile file, "/dir/file".ToU8Span(), OpenMode.Write).ThrowIfFailure();
            file.Write(0, data1, WriteOption.Flush).ThrowIfFailure();
            file.Dispose();

            // Commit and reopen the file system
            fs.Commit().ThrowIfFailure();
            fs.Dispose();

            fs = fsCreator.Create();

            // Make changes to the file
            fs.OpenFile(out file, "/dir/file".ToU8Span(), OpenMode.Write).ThrowIfFailure();
            file.Write(0, data2, WriteOption.Flush).ThrowIfFailure();
            file.Dispose();

            Assert.Success(fs.Rollback());

            // The file should contain the original data after the rollback
            var readData = new byte[data1.Length];

            Assert.Success(fs.OpenFile(out file, "/dir/file".ToU8Span(), OpenMode.Read));

            using (file)
            {
                Assert.Success(file.Read(out long bytesRead, 0, readData, ReadOption.None));
                Assert.Equal(data1.Length, bytesRead);
            }

            Assert.Equal(data1, readData);
        }
    }
}
