using NUnit.Framework;
using Robust.Shared.Utility;

namespace Robust.Shared.Tests.Utility;

[TestFixture]
[Parallelizable(ParallelScope.Fixtures | ParallelScope.All)]
[TestOf(typeof(ResPath))]
internal sealed class ResPathTest
{
    [Test]
    [TestCase("foo", ExpectedResult = "")]
    [TestCase("foo.png", ExpectedResult = "png")]
    [TestCase("test/foo.png", ExpectedResult = "png")]
    [TestCase("..png", ExpectedResult = "png")]
    [TestCase(".bashrc", ExpectedResult = "")]
    [TestCase("x.y.z", ExpectedResult = "z")]
    public string ExtensionTest(string input)
    {
        var resPathExt = new ResPath(input).Extension;
        var resourceExt = new ResPath(input).Extension;
        Assert.That(resPathExt, Is.EqualTo(resourceExt),
            message: "Found discrepancy between ResPath and ResourcePath Extension");
        return resPathExt;
    }

    [Test]
    [TestCase("", ExpectedResult = ".")]
    [TestCase(".", ExpectedResult = ".")]
    [TestCase("foo", ExpectedResult = "foo")]
    [TestCase("foo.png", ExpectedResult = "foo.png")]
    [TestCase("test/foo.png", ExpectedResult = "foo.png")]
    [TestCase("derp/.bashrc", ExpectedResult = ".bashrc")]
    [TestCase("..png", ExpectedResult = "..png")]
    [TestCase("x/y/z", ExpectedResult = "z")]
    [TestCase("/bar", ExpectedResult = "bar")]
    [TestCase("bar/", ExpectedResult = "bar")] // Trailing / gets trimmed.
    [TestCase("/foo/bar/", ExpectedResult = "bar")]
    // These next two tests are the current behaviour. I don't know if this is how it should behave, these tests just
    // ensure that it doesn't change unintentionally
    [TestCase("/foo/bar//", ExpectedResult = "")]
    [TestCase("/foo/bar///", ExpectedResult = "")]
    public string FilenameTest(string input)
    {
        var resPathFilename = new ResPath(input).Filename;
        return resPathFilename;
    }

    [Test]
    [TestCase(@"", ExpectedResult = @".")]
    [TestCase(@".", ExpectedResult = @".")]
    [TestCase(@"foo", ExpectedResult = @"foo")]
    [TestCase(@"foo.png", ExpectedResult = @"foo")]
    [TestCase(@"test/foo.png", ExpectedResult = @"foo")]
    [TestCase(@"derp/.bashrc", ExpectedResult = @".bashrc")]
    [TestCase(@"..png", ExpectedResult = @".")]
    [TestCase(@"x.y.z", ExpectedResult = @"x.y")]
    public string FilenameWithoutExtension(string input)
    {
        var resPathFileNoExt = new ResPath(input).FilenameWithoutExtension;
        return resPathFileNoExt;
    }

    [Test]
    [TestCase(@"", ExpectedResult = @".")]
    [TestCase(@".", ExpectedResult = @".")]
    [TestCase(@"/foo/bar", ExpectedResult = @"/foo")]
    [TestCase(@"/foo/bar/", ExpectedResult = @"/foo")]
    [TestCase(@"/foo/bar/x", ExpectedResult = @"/foo/bar")]
    [TestCase(@"/foo/bar.txt", ExpectedResult = @"/foo")]
    [TestCase(@"/bar.txt", ExpectedResult = @"/")]
    // These next three tests are the current behaviour. I don't know if this is how it should behave, these tests just
    // ensure that it doesn't change unintentionally
    [TestCase(@"/foo/bar//", ExpectedResult = "/foo/bar")]
    [TestCase(@"/foo/bar///", ExpectedResult = "/foo/bar/")]
    [TestCase(@"/foo/bar////", ExpectedResult = "/foo/bar//")]
    public string DirectoryTest(string path)
    {
        var resPathDirectory = new ResPath(path).Directory.ToString();
        return resPathDirectory;
    }

    [Test]
    [TestCase(@"a/b/c", "\\", ExpectedResult = @"a\b\c")]
    [TestCase(@"/a/b/c", "\\", ExpectedResult = @"\a\b\c")]
    public string ChangeSeparatorTest(string input, string separator)
    {
        return new ResPath(input).ChangeSeparator(separator);
    }

    [Test]
    [TestCase(@"a.b.c", ".")]
    [TestCase("\0a\0b\0c", "\0")]
    public void ChangeSeparatorTestException(string input, string separator)
    {
        Assert.Catch(typeof(ArgumentException), () => {new ResPath(input).ChangeSeparator(separator); });
    }

    [Test]
    [TestCase("/a/b", "c/d.png", ExpectedResult = "/a/b/c/d.png")]
    [TestCase("/a/b", "z", ExpectedResult = "/a/b/z")]
    [TestCase("/a/b", "/z", ExpectedResult = "/z")]
    [TestCase("/a/b", ".", ExpectedResult = "/a/b")]
    [TestCase("/", "/a", ExpectedResult = "/a")]
    [TestCase("/", "a", ExpectedResult = "/a")]
    public string CombineTest(string left, string right)
    {
        var pathDivRes = new ResPath(left) / new ResPath(right);
        var pathDivStr = new ResPath(left) / right;
        Assert.That(pathDivRes, Is.EqualTo(pathDivStr));
        return pathDivRes.ToString();
    }

    [Test]
    public void RootedConversionsTest()
    {
        var path = new ResPath("/a/b");
        Assert.That(path.IsRooted);
        Assert.That(path.ToRootedPath(), Is.EqualTo(path));

        var relative = path.ToRelativePath();
        Assert.That(relative, Is.EqualTo( new ResPath("a/b")));
        Assert.That(relative.IsRelative);

        Assert.That(relative.ToRelativePath(), Is.EqualTo(relative));
        Assert.That(relative.ToRootedPath(), Is.EqualTo(path));
    }

    [Test]
    [TestCase("/a/b", "/a", ExpectedResult = "b")]
    [TestCase("/a", "/", ExpectedResult = "a")]
    [TestCase("/a/b/c", "/", ExpectedResult = "a/b/c")]
    [TestCase("/a", "/a", ExpectedResult = ".")]
    [TestCase("a/b", "a", ExpectedResult = "b")]
    [TestCase("/bar/", "/", ExpectedResult = "bar")]
    [TestCase("/Textures/Weapons/laser.png", "/Textures/", ExpectedResult = "Weapons/laser.png")]
    [TestCase("foo.txt", ".", ExpectedResult = "foo.txt")]
    public string RelativeToTest(string source, string baseDir)
    {
        var path = new ResPath(source);
        var basePath = new ResPath(baseDir);
        return path.RelativeTo(basePath).ToString();
    }

    [Test]
    [TestCase("/a/b", "/b", false)]
    [TestCase("/a", "/c/d", false)]
    [TestCase("/a/b", "/a/d", false)]
    [TestCase(".", "/", false)]
    [TestCase("/", ".", false)]
    public void RelativeToFailTest(string left, string right, bool isRelative)
    {
        var path = new ResPath(left);
        var basePath = new ResPath(right);
        Assert.That(() => path.RelativeTo(basePath), Throws.ArgumentException);
    }

    [Test]
    public void WithNameTest()
    {
        var path = new ResPath("/a/b");
        var modified = path.WithName("foo");
        Assert.That(modified.Filename, Is.EqualTo("foo"));
        modified = path.WithName("foo.exe");
        Assert.That(modified.Filename, Is.EqualTo("foo.exe"));
        Assert.That(modified.Extension, Is.EqualTo("exe"));
    }

    [Test]
    public void WithNameExceptionTest()
    {
        ResPath path = new("/a/b");
        Assert.That(() => path.WithName("/foo"), Throws.ArgumentException);
        Assert.That(() => path.WithName("."), Throws.ArgumentException);
        Assert.That(() => path.WithName(""), Throws.ArgumentException);
        Assert.That(() => path.WithName(null!), Throws.ArgumentException);
    }

    [Test]
    [TestCase("/a/b.txt", "png", "/a/b.png")]
    [TestCase("/a/b.txt.bak", "png", "/a/b.txt.png")]
    public void WithExtensionTest(string start, string newExt, string expected)
    {
        var startPath = new ResPath(start);
        Assert.That(startPath.WithExtension(newExt).ToString(), Is.EqualTo(expected));
        Assert.That(startPath.WithExtension(newExt).ToString(), Is.EqualTo(expected));
    }

    [Test]
    [TestCase("/.gitignore")]
    [TestCase("")]
    [TestCase("/")]
    [TestCase("")]
    public void WithExtensionExceptionTest(string ext)
    {
        var resPath = new ResPath("/a/b");
        Assert.Catch(typeof(ArgumentException), () => { resPath.WithExtension(ext); });
    }

    [Test]
    public void RootToRelativeTest()
    {
        var path = new ResPath("/");

        Assert.That(path.ToRelativePath(), Is.EqualTo(new ResPath(".")));
    }

    [Test]
    public void TestEmptyEdgeCases()
    {
        ResPath? empty = ResPath.Empty;
        Assert.That(empty?.Extension, Is.EqualTo(""));
        Assert.That(empty?.Filename, Is.EqualTo("."));
        Assert.That(empty?.FilenameWithoutExtension, Is.EqualTo("."));
        Assert.That(empty.Equals(null), Is.False);
    }

    [Test]
    [TestCase("a", "a", ExpectedResult = true)]
    [TestCase("a", "ab", ExpectedResult = false)]
    [TestCase("", "", ExpectedResult = true)]
    [TestCase("", ".", ExpectedResult = false)]
    [TestCase(".", "", ExpectedResult = false)]
    [TestCase("/bin", "/usr", ExpectedResult = false)]
    public bool TestHashAndEquals(string left, string right)
    {
        var pathA = new ResPath(left);
        var pathB = new ResPath(right);
        Assert.That(pathA.GetHashCode() == pathB.GetHashCode(), Is.EqualTo(pathA == pathB));
        Assert.That(pathA.GetHashCode() != pathB.GetHashCode(), Is.EqualTo(pathA != pathB));
        return pathA == pathB;
    }

    [Test]
    [TestCase(@"\a\b\c", "/a/b/c")]
    [TestCase(@"\a\c", "/a/c")]
    [TestCase(@".", ".")]
    public void TestRelativeSystemPaths(string systemIn, string canonStr)
    {
        // Prevents frivolous warning, will lead to test having OS specific fails
        // ReSharper disable once RedundantArgumentDefaultValue
        var systemPath = ResPath.FromRelativeSystemPath(systemIn, '\\');
        var canonPath = new ResPath(canonStr);
        Assert.That(systemPath, Is.EqualTo(canonPath));
        Assert.That(systemPath.ToRelativeSystemPath(), Is.EqualTo(canonPath.ToRelativeSystemPath()));
    }
}
