// 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.Linq;
using System.Reflection;
using Microsoft.AspNetCore.InternalTesting;
using Xunit;

namespace Microsoft.Extensions.FileProviders.Embedded.Tests;

public class EmbeddedFileProviderTests
{
    private static readonly string Namespace = typeof(EmbeddedFileProviderTests).Namespace;

    [Fact]
    public void ConstructorWithNullAssemblyThrowsArgumentException()
    {
        Assert.Throws<ArgumentNullException>(() => new EmbeddedFileProvider(null));
    }

    [Fact]
    public void GetFileInfo_ReturnsNotFoundFileInfo_IfFileDoesNotExist()
    {
        // Arrange
        var provider = new EmbeddedFileProvider(GetType().Assembly);

        // Act
        var fileInfo = provider.GetFileInfo("DoesNotExist.Txt");

        // Assert
        Assert.NotNull(fileInfo);
        Assert.False(fileInfo.Exists);
    }

    [Theory]
    [InlineData("File.txt")]
    [InlineData("/File.txt")]
    public void GetFileInfo_ReturnsFilesAtRoot(string filePath)
    {
        // Arrange
        var provider = new EmbeddedFileProvider(GetType().Assembly);
        var expectedFileLength = 8;

        // Act
        var fileInfo = provider.GetFileInfo(filePath);

        // Assert
        Assert.NotNull(fileInfo);
        Assert.True(fileInfo.Exists);
        Assert.NotEqual(default(DateTimeOffset), fileInfo.LastModified);
        Assert.Equal(expectedFileLength, fileInfo.Length);
        Assert.False(fileInfo.IsDirectory);
        Assert.Null(fileInfo.PhysicalPath);
        Assert.Equal("File.txt", fileInfo.Name);
    }

    [Fact]
    public void GetFileInfo_ReturnsNotFoundFileInfo_IfFileDoesNotExistUnderSpecifiedNamespace()
    {
        // Arrange
        var provider = new EmbeddedFileProvider(GetType().Assembly, Namespace + ".SubNamespace");

        // Act
        var fileInfo = provider.GetFileInfo("File.txt");

        // Assert
        Assert.NotNull(fileInfo);
        Assert.False(fileInfo.Exists);
    }

    [Fact]
    public void GetFileInfo_ReturnsNotFoundIfPathStartsWithBackSlash()
    {
        // Arrange
        var provider = new EmbeddedFileProvider(GetType().Assembly);

        // Act
        var fileInfo = provider.GetFileInfo("\\File.txt");

        // Assert
        Assert.NotNull(fileInfo);
        Assert.False(fileInfo.Exists);
    }

    public static TheoryData GetFileInfo_LocatesFilesUnderSpecifiedNamespaceData
    {
        get
        {
            var theoryData = new TheoryData<string>
                {
                    "ResourcesInSubdirectory/File3.txt"
                };

            if (TestPlatformHelper.IsWindows)
            {
                theoryData.Add("ResourcesInSubdirectory\\File3.txt");
            }

            return theoryData;
        }
    }

    [Theory]
    [MemberData(nameof(GetFileInfo_LocatesFilesUnderSpecifiedNamespaceData))]
    public void GetFileInfo_LocatesFilesUnderSpecifiedNamespace(string path)
    {
        // Arrange
        var provider = new EmbeddedFileProvider(GetType().Assembly, Namespace + ".Resources");

        // Act
        var fileInfo = provider.GetFileInfo(path);

        // Assert
        Assert.NotNull(fileInfo);
        Assert.True(fileInfo.Exists);
        Assert.NotEqual(default(DateTimeOffset), fileInfo.LastModified);
        Assert.True(fileInfo.Length > 0);
        Assert.False(fileInfo.IsDirectory);
        Assert.Null(fileInfo.PhysicalPath);
        Assert.Equal("File3.txt", fileInfo.Name);
    }

    public static TheoryData GetFileInfo_LocatesFilesUnderSubDirectoriesData
    {
        get
        {
            var theoryData = new TheoryData<string>
                {
                    "Resources/File.txt"
                };

            if (TestPlatformHelper.IsWindows)
            {
                theoryData.Add("Resources\\File.txt");
            }

            return theoryData;
        }
    }

    [Theory]
    [MemberData(nameof(GetFileInfo_LocatesFilesUnderSubDirectoriesData))]
    public void GetFileInfo_LocatesFilesUnderSubDirectories(string path)
    {
        // Arrange
        var provider = new EmbeddedFileProvider(GetType().Assembly);

        // Act
        var fileInfo = provider.GetFileInfo(path);

        // Assert
        Assert.NotNull(fileInfo);
        Assert.True(fileInfo.Exists);
        Assert.NotEqual(default(DateTimeOffset), fileInfo.LastModified);
        Assert.True(fileInfo.Length > 0);
        Assert.False(fileInfo.IsDirectory);
        Assert.Null(fileInfo.PhysicalPath);
        Assert.Equal("File.txt", fileInfo.Name);
    }

    public static TheoryData GetFileInfo_LocatesFilesUnderSubDirectories_IfDirectoriesContainsInvalidEverettCharData
    {
        get
        {
            var theoryData = new TheoryData<string>
                {
                    "sub/sub-dir/File3.txt"
                };

            if (TestPlatformHelper.IsWindows)
            {
                theoryData.Add("sub\\sub-dir\\File3.txt");
            }

            return theoryData;
        }
    }

    [Theory]
    [MemberData(nameof(GetFileInfo_LocatesFilesUnderSubDirectories_IfDirectoriesContainsInvalidEverettCharData))]
    public void GetFileInfo_LocatesFilesUnderSubDirectories_IfDirectoriesContainsInvalidEverettChar(string path)
    {
        // Arrange
        var provider = new EmbeddedFileProvider(GetType().Assembly);

        // Act
        var fileInfo = provider.GetFileInfo(path);

        // Assert
        Assert.NotNull(fileInfo);
        Assert.True(fileInfo.Exists);
        Assert.NotEqual(default(DateTimeOffset), fileInfo.LastModified);
        Assert.True(fileInfo.Length > 0);
        Assert.False(fileInfo.IsDirectory);
        Assert.Null(fileInfo.PhysicalPath);
        Assert.Equal("File3.txt", fileInfo.Name);
    }

    [Theory]
    [InlineData("")]
    [InlineData("/")]
    public void GetDirectoryContents_ReturnsAllFilesInFileSystem(string path)
    {
        // Arrange
        var provider = new EmbeddedFileProvider(GetType().Assembly, Namespace + ".Resources");

        // Act
        var files = provider.GetDirectoryContents(path);

        // Assert
        Assert.Collection(files.OrderBy(f => f.Name, StringComparer.Ordinal),
            file => Assert.Equal("File.txt", file.Name),
            file => Assert.Equal("ResourcesInSubdirectory.File3.txt", file.Name));

        Assert.False(provider.GetDirectoryContents("file").Exists);
        Assert.False(provider.GetDirectoryContents("file/").Exists);
        Assert.False(provider.GetDirectoryContents("file.txt").Exists);
        Assert.False(provider.GetDirectoryContents("file/txt").Exists);
    }

    [Fact]
    public void GetDirectoryContents_ReturnsEmptySequence_IfResourcesDoNotExistUnderNamespace()
    {
        // Arrange
        var provider = new EmbeddedFileProvider(GetType().Assembly, "Unknown.Namespace");

        // Act
        var files = provider.GetDirectoryContents(string.Empty);

        // Assert
        Assert.NotNull(files);
        Assert.True(files.Exists);
        Assert.Empty(files);
    }

    [Theory]
    [InlineData("Resources")]
    [InlineData("/Resources")]
    public void GetDirectoryContents_ReturnsNotFoundDirectoryContents_IfHierarchicalPathIsSpecified(string path)
    {
        // Arrange
        var provider = new EmbeddedFileProvider(GetType().Assembly);

        // Act
        var files = provider.GetDirectoryContents(path);

        // Assert
        Assert.NotNull(files);
        Assert.False(files.Exists);
        Assert.Empty(files);
    }

    [Fact]
    public void Watch_ReturnsNoOpTrigger()
    {
        // Arrange
        var provider = new EmbeddedFileProvider(GetType().Assembly);

        // Act
        var token = provider.Watch("Resources/File.txt");

        // Assert
        Assert.NotNull(token);
        Assert.False(token.ActiveChangeCallbacks);
        Assert.False(token.HasChanged);
    }
}
