// <copyright file="DirectoryServiceTests.cs" company="PlaceholderCompany">
// Copyright (c) PlaceholderCompany. All rights reserved.
// </copyright>

namespace Jellyfin.Controller.Tests
{
    using System.Collections.Generic;
    using System.Linq;
    using MediaBrowser.Controller.Providers;
    using MediaBrowser.Model.IO;
    using Moq;
    using Xunit;

    /// <summary>
    /// Tests for <see cref="DirectoryService"/>.
    /// </summary>
    public class DirectoryServiceTests
    {
        private const string LowerCasePath = "/music/someartist";
        private const string UpperCasePath = "/music/SOMEARTIST";

        private static readonly FileSystemMetadata[] LowerCaseFileSystemMetadata =
        [
            new()
            {
                FullName = LowerCasePath + "/Artwork",
                IsDirectory = true,
            },
            new()
            {
                FullName = LowerCasePath + "/Some Other Folder",
                IsDirectory = true,
            },
            new()
            {
                FullName = LowerCasePath + "/Song 2.mp3",
                IsDirectory = false,
            },
            new()
            {
                FullName = LowerCasePath + "/Song 3.mp3",
                IsDirectory = false,
            },
        ];

        private static readonly FileSystemMetadata[] UpperCaseFileSystemMetadata =
        [
            new()
            {
                FullName = UpperCasePath + "/Lyrics",
                IsDirectory = true,
            },
            new()
            {
                FullName = UpperCasePath + "/Song 1.mp3",
                IsDirectory = false,
            },
        ];

        /// <summary>
        /// Tests that GetFileSystemEntries caches entries for paths with different casing.
        /// </summary>
        [Fact]
        public void GetFileSystemEntries_GivenPathsWithDifferentCasing_CachesAll()
        {
            Mock<IFileSystem> fileSystemMock = new();
            _ = fileSystemMock.Setup(f => f.GetFileSystemEntries(It.Is<string>(x => x == UpperCasePath), false)).Returns(UpperCaseFileSystemMetadata);
            _ = fileSystemMock.Setup(f => f.GetFileSystemEntries(It.Is<string>(x => x == LowerCasePath), false)).Returns(LowerCaseFileSystemMetadata);
            DirectoryService directoryService = new(fileSystemMock.Object);

            FileSystemMetadata[] upperCaseResult = directoryService.GetFileSystemEntries(UpperCasePath);
            FileSystemMetadata[] lowerCaseResult = directoryService.GetFileSystemEntries(LowerCasePath);

            Assert.Equal(UpperCaseFileSystemMetadata, upperCaseResult);
            Assert.Equal(LowerCaseFileSystemMetadata, lowerCaseResult);
        }

        /// <summary>
        /// Tests that GetFiles returns correct files for paths with different casing.
        /// </summary>
        [Fact]
        public void GetFiles_GivenPathsWithDifferentCasing_ReturnsCorrectFiles()
        {
            Mock<IFileSystem> fileSystemMock = new();
            _ = fileSystemMock.Setup(f => f.GetFileSystemEntries(It.Is<string>(x => x == UpperCasePath), false)).Returns(UpperCaseFileSystemMetadata);
            _ = fileSystemMock.Setup(f => f.GetFileSystemEntries(It.Is<string>(x => x == LowerCasePath), false)).Returns(LowerCaseFileSystemMetadata);
            DirectoryService directoryService = new(fileSystemMock.Object);

            List<FileSystemMetadata> upperCaseResult = directoryService.GetFiles(UpperCasePath);
            List<FileSystemMetadata> lowerCaseResult = directoryService.GetFiles(LowerCasePath);

            Assert.Equal(UpperCaseFileSystemMetadata.Where(f => !f.IsDirectory), upperCaseResult);
            Assert.Equal(LowerCaseFileSystemMetadata.Where(f => !f.IsDirectory), lowerCaseResult);
        }

        /// <summary>
        /// Tests that GetDirectories returns correct directories for paths with different casing.
        /// </summary>
        [Fact]
        public void GetDirectories_GivenPathsWithDifferentCasing_ReturnsCorrectDirectories()
        {
            Mock<IFileSystem> fileSystemMock = new();
            _ = fileSystemMock.Setup(f => f.GetFileSystemEntries(It.Is<string>(x => x == UpperCasePath), false)).Returns(UpperCaseFileSystemMetadata);
            _ = fileSystemMock.Setup(f => f.GetFileSystemEntries(It.Is<string>(x => x == LowerCasePath), false)).Returns(LowerCaseFileSystemMetadata);
            DirectoryService directoryService = new(fileSystemMock.Object);

            List<FileSystemMetadata> upperCaseResult = directoryService.GetDirectories(UpperCasePath);
            List<FileSystemMetadata> lowerCaseResult = directoryService.GetDirectories(LowerCasePath);

            Assert.Equal(UpperCaseFileSystemMetadata.Where(f => f.IsDirectory), upperCaseResult);
            Assert.Equal(LowerCaseFileSystemMetadata.Where(f => f.IsDirectory), lowerCaseResult);
        }

        /// <summary>
        /// Tests that GetFile returns correct file for paths with different casing.
        /// </summary>
        [Fact]
        public void GetFile_GivenFilePathsWithDifferentCasing_ReturnsCorrectFile()
        {
            const string lowerCasePath = "/music/someartist/song 1.mp3";
            FileSystemMetadata lowerCaseFileSystemMetadata = new()
            {
                FullName = lowerCasePath,
                Exists = true,
            };
            const string upperCasePath = "/music/SOMEARTIST/SONG 1.mp3";
            FileSystemMetadata upperCaseFileSystemMetadata = new()
            {
                FullName = upperCasePath,
                Exists = false,
            };
            Mock<IFileSystem> fileSystemMock = new();
            _ = fileSystemMock.Setup(f => f.GetFileSystemInfo(It.Is<string>(x => x == upperCasePath))).Returns(upperCaseFileSystemMetadata);
            _ = fileSystemMock.Setup(f => f.GetFileSystemInfo(It.Is<string>(x => x == lowerCasePath))).Returns(lowerCaseFileSystemMetadata);
            DirectoryService directoryService = new(fileSystemMock.Object);

            FileSystemMetadata? lowerCaseDirResult = directoryService.GetDirectory(lowerCasePath);
            FileSystemMetadata? lowerCaseFileResult = directoryService.GetFile(lowerCasePath);
            FileSystemMetadata? upperCaseDirResult = directoryService.GetDirectory(upperCasePath);
            FileSystemMetadata? upperCaseFileResult = directoryService.GetFile(upperCasePath);

            Assert.Null(lowerCaseDirResult);
            Assert.Equal(lowerCaseFileSystemMetadata, lowerCaseFileResult);
            Assert.Null(upperCaseDirResult);
            Assert.Null(upperCaseFileResult);
        }

        /// <summary>
        /// Tests that GetDirectory returns correct directory for paths with different casing.
        /// </summary>
        [Fact]
        public void GetDirectory_GivenFilePathsWithDifferentCasing_ReturnsCorrectDirectory()
        {
            const string lowerCasePath = "/music/someartist/Lyrics";
            FileSystemMetadata lowerCaseFileSystemMetadata = new()
            {
                FullName = lowerCasePath,
                IsDirectory = true,
                Exists = true,
            };
            const string upperCasePath = "/music/SOMEARTIST/LYRICS";
            FileSystemMetadata upperCaseFileSystemMetadata = new()
            {
                FullName = upperCasePath,
                IsDirectory = true,
                Exists = false,
            };
            Mock<IFileSystem> fileSystemMock = new();
            _ = fileSystemMock.Setup(f => f.GetFileSystemInfo(It.Is<string>(x => x == upperCasePath))).Returns(upperCaseFileSystemMetadata);
            _ = fileSystemMock.Setup(f => f.GetFileSystemInfo(It.Is<string>(x => x == lowerCasePath))).Returns(lowerCaseFileSystemMetadata);
            DirectoryService directoryService = new(fileSystemMock.Object);

            FileSystemMetadata? lowerCaseDirResult = directoryService.GetDirectory(lowerCasePath);
            FileSystemMetadata? lowerCaseFileResult = directoryService.GetFile(lowerCasePath);
            FileSystemMetadata? upperCaseDirResult = directoryService.GetDirectory(upperCasePath);
            FileSystemMetadata? upperCaseFileResult = directoryService.GetFile(upperCasePath);

            Assert.Equal(lowerCaseFileSystemMetadata, lowerCaseDirResult);
            Assert.Null(lowerCaseFileResult);
            Assert.Null(upperCaseDirResult);
            Assert.Null(upperCaseFileResult);
        }

        /// <summary>
        /// Tests that GetFile returns cached file when the path is already cached.
        /// </summary>
        [Fact]
        public void GetFile_GivenCachedPath_ReturnsCachedFile()
        {
            const string path = "/music/someartist/song 1.mp3";
            FileSystemMetadata cachedFileSystemMetadata = new()
            {
                FullName = path,
                Exists = true,
            };
            FileSystemMetadata newFileSystemMetadata = new()
            {
                FullName = "/music/SOMEARTIST/song 1.mp3",
                Exists = true,
            };

            Mock<IFileSystem> fileSystemMock = new();
            _ = fileSystemMock.Setup(f => f.GetFileSystemInfo(It.Is<string>(x => x == path))).Returns(cachedFileSystemMetadata);
            DirectoryService directoryService = new(fileSystemMock.Object);

            FileSystemMetadata? result = directoryService.GetFile(path);
            _ = fileSystemMock.Setup(f => f.GetFileSystemInfo(It.Is<string>(x => x == path))).Returns(newFileSystemMetadata);
            FileSystemMetadata? secondResult = directoryService.GetFile(path);

            Assert.Equivalent(cachedFileSystemMetadata, result);
            Assert.Equivalent(cachedFileSystemMetadata, secondResult);
        }

        /// <summary>
        /// Tests that GetFilePaths returns only cached paths when clear is not called.
        /// </summary>
        [Fact]
        public void GetFilePaths_GivenCachedFilePathWithoutClear_ReturnsOnlyCachedPaths()
        {
            const string path = "/music/someartist";

            string[] cachedPaths =
            [
                "/music/someartist/song 1.mp3",
                "/music/someartist/song 2.mp3",
                "/music/someartist/song 3.mp3",
                "/music/someartist/song 4.mp3",
            ];
            string[] newPaths =
            [
                "/music/someartist/song 5.mp3",
                "/music/someartist/song 6.mp3",
                "/music/someartist/song 7.mp3",
                "/music/someartist/song 8.mp3",
            ];

            Mock<IFileSystem> fileSystemMock = new();
            _ = fileSystemMock.Setup(f => f.GetFilePaths(It.Is<string>(x => x == path), false)).Returns(cachedPaths);
            DirectoryService directoryService = new(fileSystemMock.Object);

            IReadOnlyList<string> result = directoryService.GetFilePaths(path);
            _ = fileSystemMock.Setup(f => f.GetFilePaths(It.Is<string>(x => x == path), false)).Returns(newPaths);
            IReadOnlyList<string> secondResult = directoryService.GetFilePaths(path);

            Assert.Equal(cachedPaths, result);
            Assert.Equal(cachedPaths, secondResult);
        }

        /// <summary>
        /// Tests that GetFilePaths returns new paths when clear is called.
        /// </summary>
        [Fact]
        public void GetFilePaths_GivenCachedFilePathWithClear_ReturnsNewPaths()
        {
            const string path = "/music/someartist";

            string[] cachedPaths =
            [
                "/music/someartist/song 1.mp3",
                "/music/someartist/song 2.mp3",
                "/music/someartist/song 3.mp3",
                "/music/someartist/song 4.mp3",
            ];
            string[] newPaths =
            [
                "/music/someartist/song 5.mp3",
                "/music/someartist/song 6.mp3",
                "/music/someartist/song 7.mp3",
                "/music/someartist/song 8.mp3",
            ];

            Mock<IFileSystem> fileSystemMock = new();
            _ = fileSystemMock.Setup(f => f.GetFilePaths(It.Is<string>(x => x == path), false)).Returns(cachedPaths);
            DirectoryService directoryService = new(fileSystemMock.Object);

            IReadOnlyList<string> result = directoryService.GetFilePaths(path);
            _ = fileSystemMock.Setup(f => f.GetFilePaths(It.Is<string>(x => x == path), false)).Returns(newPaths);
            IReadOnlyList<string> secondResult = directoryService.GetFilePaths(path, true);

            Assert.Equal(cachedPaths, result);
            Assert.Equal(newPaths, secondResult);
        }
    }
}
