// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.IO;
using System.Linq;
using Microsoft.Extensions.FileProviders.Embedded.Manifest;
using Xunit;

namespace Microsoft.Extensions.FileProviders;

public class ManifestEmbeddedFileProviderTests
{
    [Fact]
    public void GetFileInfo_CanResolveSimpleFiles()
    {
        // Arrange
        var assembly = new TestAssembly(
            TestEntry.Directory("unused",
                TestEntry.File("jquery.validate.js"),
                TestEntry.File("jquery.min.js"),
                TestEntry.File("site.css")));

        // Act
        var provider = new ManifestEmbeddedFileProvider(assembly);

        // Assert
        var jqueryValidate = provider.GetFileInfo("jquery.validate.js");
        Assert.True(jqueryValidate.Exists);
        Assert.False(jqueryValidate.IsDirectory);
        Assert.Equal("jquery.validate.js", jqueryValidate.Name);
        Assert.Null(jqueryValidate.PhysicalPath);
        Assert.Equal(0, jqueryValidate.Length);

        var jqueryMin = provider.GetFileInfo("jquery.min.js");
        Assert.True(jqueryMin.Exists);
        Assert.False(jqueryMin.IsDirectory);
        Assert.Equal("jquery.min.js", jqueryMin.Name);
        Assert.Null(jqueryMin.PhysicalPath);
        Assert.Equal(0, jqueryMin.Length);

        var siteCss = provider.GetFileInfo("site.css");
        Assert.True(siteCss.Exists);
        Assert.False(siteCss.IsDirectory);
        Assert.Equal("site.css", siteCss.Name);
        Assert.Null(siteCss.PhysicalPath);
        Assert.Equal(0, siteCss.Length);
    }

    [Fact]
    public void GetFileInfo_CanResolveFilesInsideAFolder()
    {
        // Arrange
        var assembly = new TestAssembly(
            TestEntry.Directory("unused",
                TestEntry.Directory("wwwroot",
                    TestEntry.File("jquery.validate.js"),
                    TestEntry.File("jquery.min.js"),
                    TestEntry.File("site.css"))));

        // Act
        var provider = new ManifestEmbeddedFileProvider(assembly);

        // Assert
        var jqueryValidate = provider.GetFileInfo(Path.Combine("wwwroot", "jquery.validate.js"));
        Assert.True(jqueryValidate.Exists);
        Assert.False(jqueryValidate.IsDirectory);
        Assert.Equal("jquery.validate.js", jqueryValidate.Name);
        Assert.Null(jqueryValidate.PhysicalPath);
        Assert.Equal(0, jqueryValidate.Length);

        var jqueryMin = provider.GetFileInfo(Path.Combine("wwwroot", "jquery.min.js"));
        Assert.True(jqueryMin.Exists);
        Assert.False(jqueryMin.IsDirectory);
        Assert.Equal("jquery.min.js", jqueryMin.Name);
        Assert.Null(jqueryMin.PhysicalPath);
        Assert.Equal(0, jqueryMin.Length);

        var siteCss = provider.GetFileInfo(Path.Combine("wwwroot", "site.css"));
        Assert.True(siteCss.Exists);
        Assert.False(siteCss.IsDirectory);
        Assert.Equal("site.css", siteCss.Name);
        Assert.Null(siteCss.PhysicalPath);
        Assert.Equal(0, siteCss.Length);
    }

    [Fact]
    public void GetFileInfo_ResolveNonExistingFile_ReturnsNotFoundFileInfo()
    {
        // Arrange
        var assembly = new TestAssembly(
            TestEntry.Directory("unused",
                TestEntry.Directory("wwwroot",
                    TestEntry.File("jquery.validate.js"),
                    TestEntry.File("jquery.min.js"),
                    TestEntry.File("site.css"))));

        var provider = new ManifestEmbeddedFileProvider(assembly);

        // Act
        var file = provider.GetFileInfo("some/non/existing/file.txt");

        // Assert
        Assert.IsType<NotFoundFileInfo>(file);
    }

    [Fact]
    public void GetFileInfo_ResolveNonExistingDirectory_ReturnsNotFoundFileInfo()
    {
        // Arrange
        var assembly = new TestAssembly(
            TestEntry.Directory("unused",
                TestEntry.Directory("wwwroot",
                    TestEntry.File("jquery.validate.js"),
                    TestEntry.File("jquery.min.js"),
                    TestEntry.File("site.css"))));

        var provider = new ManifestEmbeddedFileProvider(assembly);

        // Act
        var file = provider.GetFileInfo("some");

        // Assert
        Assert.IsType<NotFoundFileInfo>(file);
    }

    [Fact]
    public void GetFileInfo_ResolveExistingDirectory_ReturnsNotFoundFileInfo()
    {
        // Arrange
        var assembly = new TestAssembly(
            TestEntry.Directory("unused",
                TestEntry.Directory("wwwroot",
                    TestEntry.File("jquery.validate.js"),
                    TestEntry.File("jquery.min.js"),
                    TestEntry.File("site.css"))));

        var provider = new ManifestEmbeddedFileProvider(assembly);

        // Act
        var file = provider.GetFileInfo("wwwroot");

        // Assert
        Assert.IsType<NotFoundFileInfo>(file);
    }

    [Theory]
    [InlineData("WWWROOT", "JQUERY.VALIDATE.JS")]
    [InlineData("WwWRoOT", "JQuERY.VALiDATE.js")]
    public void GetFileInfo_ResolvesFiles_WithDifferentCasing(string folder, string file)
    {
        // Arrange
        var assembly = new TestAssembly(
            TestEntry.Directory("unused",
                TestEntry.Directory("wwwroot",
                    TestEntry.File("jquery.validate.js"),
                    TestEntry.File("jquery.min.js"),
                    TestEntry.File("site.css"))));

        // Act
        var provider = new ManifestEmbeddedFileProvider(assembly);

        // Assert
        var jqueryValidate = provider.GetFileInfo(Path.Combine(folder, file));
        Assert.True(jqueryValidate.Exists);
        Assert.False(jqueryValidate.IsDirectory);
        Assert.Equal("jquery.validate.js", jqueryValidate.Name);
        Assert.Null(jqueryValidate.PhysicalPath);
        Assert.Equal(0, jqueryValidate.Length);
    }

    [Fact]
    public void GetFileInfo_AllowsLeadingDots_OnThePath()
    {
        // Arrange
        var assembly = new TestAssembly(
            TestEntry.Directory("unused",
                TestEntry.Directory("wwwroot",
                    TestEntry.File("jquery.validate.js"),
                    TestEntry.File("jquery.min.js"),
                    TestEntry.File("site.css"))));

        // Act
        var provider = new ManifestEmbeddedFileProvider(assembly);

        // Assert
        var jqueryValidate = provider.GetFileInfo(Path.Combine(".", "wwwroot", "jquery.validate.js"));
        Assert.True(jqueryValidate.Exists);
        Assert.False(jqueryValidate.IsDirectory);
        Assert.Equal("jquery.validate.js", jqueryValidate.Name);
        Assert.Null(jqueryValidate.PhysicalPath);
        Assert.Equal(0, jqueryValidate.Length);
    }

    [Fact]
    public void GetFileInfo_EscapingFromTheRootFolder_ReturnsNotFound()
    {
        // Arrange
        var assembly = new TestAssembly(
            TestEntry.Directory("unused",
                TestEntry.Directory("wwwroot",
                    TestEntry.File("jquery.validate.js"),
                    TestEntry.File("jquery.min.js"),
                    TestEntry.File("site.css"))));

        // Act
        var provider = new ManifestEmbeddedFileProvider(assembly);

        // Assert
        var jqueryValidate = provider.GetFileInfo(Path.Combine("..", "wwwroot", "jquery.validate.js"));
        Assert.IsType<NotFoundFileInfo>(jqueryValidate);
    }

    [Theory]
    [InlineData("wwwroot/jquery?validate.js")]
    [InlineData("wwwroot/jquery*validate.js")]
    [InlineData("wwwroot/jquery:validate.js")]
    [InlineData("wwwroot/jquery<validate.js")]
    [InlineData("wwwroot/jquery>validate.js")]
    [InlineData("wwwroot/jquery\0validate.js")]
    public void GetFileInfo_ReturnsNotFoundfileInfo_ForPathsWithInvalidCharacters(string path)
    {
        // Arrange
        var assembly = new TestAssembly(
            TestEntry.Directory("unused",
                TestEntry.Directory("wwwroot",
                    TestEntry.File("jquery.validate.js"),
                    TestEntry.File("jquery.min.js"),
                    TestEntry.File("site.css"))));

        // Act
        var provider = new ManifestEmbeddedFileProvider(assembly);

        // Assert
        var file = provider.GetFileInfo(path);
        Assert.IsType<NotFoundFileInfo>(file);
        Assert.Equal(path, file.Name);
    }

    [Fact]
    public void GetDirectoryContents_CanEnumerateExistingFolders()
    {
        // Arrange
        var assembly = new TestAssembly(
            TestEntry.Directory("unused",
                TestEntry.Directory("wwwroot",
                    TestEntry.File("jquery.validate.js"),
                    TestEntry.File("jquery.min.js"),
                    TestEntry.File("site.css"))));

        var provider = new ManifestEmbeddedFileProvider(assembly);

        var expectedContents = new[]
        {
                CreateTestFileInfo("jquery.validate.js"),
                CreateTestFileInfo("jquery.min.js"),
                CreateTestFileInfo("site.css")
            };

        // Act
        var contents = provider.GetDirectoryContents("wwwroot").ToArray();

        // Assert
        Assert.Equal(expectedContents, contents, FileInfoComparer.Instance);
    }

    [Fact]
    public void GetDirectoryContents_EnumeratesOnlyAGivenLevel()
    {
        // Arrange
        var assembly = new TestAssembly(
            TestEntry.Directory("unused",
                TestEntry.Directory("wwwroot",
                    TestEntry.File("jquery.validate.js"),
                    TestEntry.File("jquery.min.js"),
                    TestEntry.File("site.css"))));

        var provider = new ManifestEmbeddedFileProvider(assembly);

        var expectedContents = new[]
        {
                CreateTestFileInfo("wwwroot", isDirectory: true)
            };

        // Act
        var contents = provider.GetDirectoryContents(".").ToArray();

        // Assert
        Assert.Equal(expectedContents, contents, FileInfoComparer.Instance);
    }

    [Fact]
    public void GetDirectoryContents_EnumeratesFilesAndDirectoriesOnAGivenPath()
    {
        // Arrange
        var assembly = new TestAssembly(
            TestEntry.Directory("unused",
                TestEntry.Directory("wwwroot"),
                TestEntry.File("site.css")));

        var provider = new ManifestEmbeddedFileProvider(assembly);

        var expectedContents = new[]
        {
                CreateTestFileInfo("wwwroot", isDirectory: true),
                CreateTestFileInfo("site.css")
            };

        // Act
        var contents = provider.GetDirectoryContents(".").ToArray();

        // Assert
        Assert.Equal(expectedContents, contents, FileInfoComparer.Instance);
    }

    [Fact]
    public void GetDirectoryContents_ReturnsNoEntries_ForNonExistingDirectories()
    {
        // Arrange
        var assembly = new TestAssembly(
            TestEntry.Directory("unused",
                TestEntry.Directory("wwwroot"),
                TestEntry.File("site.css")));

        var provider = new ManifestEmbeddedFileProvider(assembly);

        // Act
        var contents = provider.GetDirectoryContents("non-existing");

        // Assert
        Assert.IsType<NotFoundDirectoryContents>(contents);
    }

    [Fact]
    public void GetDirectoryContents_ReturnsNoEntries_ForFilePaths()
    {
        // Arrange
        var assembly = new TestAssembly(
            TestEntry.Directory("unused",
                TestEntry.Directory("wwwroot"),
                TestEntry.File("site.css")));

        var provider = new ManifestEmbeddedFileProvider(assembly);

        // Act
        var contents = provider.GetDirectoryContents("site.css");

        // Assert
        Assert.IsType<NotFoundDirectoryContents>(contents);
    }

    [Theory]
    [InlineData("wwwro*t")]
    [InlineData("wwwro?t")]
    [InlineData("wwwro:t")]
    [InlineData("wwwro<t")]
    [InlineData("wwwro>t")]
    [InlineData("wwwro\0t")]
    public void GetDirectoryContents_ReturnsNotFoundDirectoryContents_ForPathsWithInvalidCharacters(string path)
    {
        // Arrange
        var assembly = new TestAssembly(
            TestEntry.Directory("unused",
                TestEntry.Directory("wwwroot",
                    TestEntry.File("jquery.validate.js"),
                    TestEntry.File("jquery.min.js"),
                    TestEntry.File("site.css"))));

        // Act
        var provider = new ManifestEmbeddedFileProvider(assembly);

        // Assert
        var directory = provider.GetDirectoryContents(path);
        Assert.IsType<NotFoundDirectoryContents>(directory);
    }

    [Fact]
    public void GetDirectoryContents_ReturnsDirectoryInfoThatImplementsIDirectoryContents_ForDirectory()
    {
        // Arrange
        var assembly = new TestAssembly(
            TestEntry.Directory("unused",
                TestEntry.Directory("wwwroot",
                    TestEntry.File("site.css")),
                TestEntry.File("data.json"),
                TestEntry.Directory("config",
                    TestEntry.File("release.json"),
                    TestEntry.File("testing.json"))));

        // Act
        var provider = new ManifestEmbeddedFileProvider(assembly);
        var root = provider.GetDirectoryContents("/").ToDictionary(f => f.Name);

        var rootExpected = new[]
        {
            CreateTestFileInfo("wwwroot", isDirectory: true),
            CreateTestFileInfo("data.json"),
            CreateTestFileInfo("config", isDirectory: true)
        };

        var wwwrootExpected = new[]
        {
            CreateTestFileInfo("site.css")
        };

        var configExpected = new[]
        {
            CreateTestFileInfo("release.json"),
            CreateTestFileInfo("testing.json")
        };

        // Assert

        Assert.IsAssignableFrom<IDirectoryContents>(root["wwwroot"]);
        Assert.IsAssignableFrom<IDirectoryContents>(root["config"]);

        Assert.Equal(rootExpected, root.Values, FileInfoComparer.Instance);
        Assert.Equal(wwwrootExpected, (IDirectoryContents)root["wwwroot"], FileInfoComparer.Instance);
        Assert.Equal(configExpected, (IDirectoryContents)root["config"], FileInfoComparer.Instance);
    }

    [Fact]
    public void Contructor_CanScopeManifestToAFolder()
    {
        // Arrange
        var assembly = new TestAssembly(
            TestEntry.Directory("unused",
                TestEntry.Directory("wwwroot",
                    TestEntry.File("jquery.validate.js")),
                TestEntry.File("site.css")));

        var provider = new ManifestEmbeddedFileProvider(assembly);
        var scopedProvider = new ManifestEmbeddedFileProvider(assembly, provider.Manifest.Scope("wwwroot"), DateTimeOffset.UtcNow);

        // Act
        var jqueryValidate = scopedProvider.GetFileInfo("jquery.validate.js");

        // Assert
        Assert.True(jqueryValidate.Exists);
        Assert.False(jqueryValidate.IsDirectory);
        Assert.Equal("jquery.validate.js", jqueryValidate.Name);
        Assert.Null(jqueryValidate.PhysicalPath);
        Assert.Equal(0, jqueryValidate.Length);
    }

    [Theory]
    [InlineData("wwwroot/jquery.validate.js")]
    [InlineData("../wwwroot/jquery.validate.js")]
    [InlineData("site.css")]
    [InlineData("../site.css")]
    public void ScopedFileProvider_DoesNotReturnFilesOutOfScope(string path)
    {
        // Arrange
        var assembly = new TestAssembly(
            TestEntry.Directory("unused",
                TestEntry.Directory("wwwroot",
                    TestEntry.File("jquery.validate.js")),
                TestEntry.File("site.css")));

        var provider = new ManifestEmbeddedFileProvider(assembly);
        var scopedProvider = new ManifestEmbeddedFileProvider(assembly, provider.Manifest.Scope("wwwroot"), DateTimeOffset.UtcNow);

        // Act
        var jqueryValidate = scopedProvider.GetFileInfo(path);

        // Assert
        Assert.IsType<NotFoundFileInfo>(jqueryValidate);
    }

    private IFileInfo CreateTestFileInfo(string name, bool isDirectory = false) =>
        new TestFileInfo(name, isDirectory);
}
