using System;
using System.Diagnostics;
using System.IO;

using AIRuntime.Runtime.Filesystem;

using NUnit.Framework;


namespace AIRuntime.Tests
{
    public sealed class WorkspaceTests
    {
        private string _testRoot;

        private string _workspacePath;

        private string _externalPath;

        private string _linkPath;

        private Workspace _workspace;


        [SetUp]
        public void SetUp()
        {
            _testRoot =
                Path.Combine(
                    Path.GetTempPath(),
                    "AIRuntime.WorkspaceTests",
                    Guid.NewGuid().ToString("N"));

            _workspacePath =
                Path.Combine(
                    _testRoot,
                    "Workspace");

            _externalPath =
                Path.Combine(
                    _testRoot,
                    "External");

            _linkPath =
                Path.Combine(
                    _workspacePath,
                    "escape-link");

            Directory.CreateDirectory(
                _externalPath);

            _workspace =
                new Workspace(
                    _workspacePath);
        }


        [TearDown]
        public void TearDown()
        {
            DeleteLinkIfPresent();

            if (!string.IsNullOrWhiteSpace(_testRoot) &&
                Directory.Exists(_testRoot))
            {
                Directory.Delete(
                    _testRoot,
                    recursive: true);
            }
        }


        [Test]
        public void ResolveAcceptsRelativePathsAndWorkspaceRoot()
        {
            Assert.AreEqual(
                _workspace.Root,
                _workspace.Resolve("."));

            Assert.AreEqual(
                Path.Combine(
                    _workspace.Root,
                    "folder",
                    "file.txt"),
                _workspace.Resolve(
                    Path.Combine(
                        "folder",
                        "file.txt")));
        }


        [Test]
        public void ResolveRejectsParentTraversal()
        {
            Assert.Throws<UnauthorizedAccessException>(
                () => _workspace.Resolve(
                    Path.Combine(
                        "..",
                        "External",
                        "secret.txt")));
        }


        [Test]
        public void ResolveRejectsAbsolutePaths()
        {
            Assert.Throws<UnauthorizedAccessException>(
                () => _workspace.Resolve(
                    Path.Combine(
                        _externalPath,
                        "secret.txt")));

            Assert.Throws<UnauthorizedAccessException>(
                () => _workspace.Resolve(
                    Path.Combine(
                        _workspace.Root,
                        "inside.txt")));
        }


        [Test]
        public void IsInsideWorkspaceNormalizesTraversal()
        {
            string disguisedEscape =
                Path.Combine(
                    _workspace.Root,
                    "folder",
                    "..",
                    "..",
                    "External",
                    "secret.txt");

            Assert.IsFalse(
                _workspace.IsInsideWorkspace(
                    disguisedEscape));
        }


        [Test]
        public void IsInsideWorkspaceUsesPlatformPathComparison()
        {
            string caseVariant =
                ToggleFirstLetterCase(
                    _workspace.Root);

            bool expected =
                Path.DirectorySeparatorChar == '\\';

            Assert.AreEqual(
                expected,
                _workspace.IsInsideWorkspace(
                    Path.Combine(
                        caseVariant,
                        "file.txt")));
        }


        [Test]
        public void ResolveRejectsLinkOrJunctionEscape()
        {
            if (!TryCreateDirectoryLink(
                    _linkPath,
                    _externalPath,
                    out string failure))
            {
                Assert.Ignore(
                    $"Directory links are unavailable in this environment: {failure}");
            }

            File.WriteAllText(
                Path.Combine(
                    _externalPath,
                    "secret.txt"),
                "outside");

            Assert.Throws<UnauthorizedAccessException>(
                () => _workspace.Resolve(
                    Path.Combine(
                        "escape-link",
                        "secret.txt")));
        }


        private static string ToggleFirstLetterCase(
            string value)
        {
            char[] characters = value.ToCharArray();

            for (int index = 0;
                 index < characters.Length;
                 index++)
            {
                if (!char.IsLetter(characters[index]))
                    continue;

                characters[index] =
                    char.IsUpper(characters[index])
                        ? char.ToLowerInvariant(characters[index])
                        : char.ToUpperInvariant(characters[index]);

                return new string(characters);
            }

            Assert.Fail(
                "The temporary workspace path did not contain a letter.");

            return value;
        }


        private static bool TryCreateDirectoryLink(
            string linkPath,
            string targetPath,
            out string failure)
        {
            bool isWindows =
                Path.DirectorySeparatorChar == '\\';

            var startInfo =
                new ProcessStartInfo
                {
                    FileName =
                        isWindows
                            ? "cmd.exe"
                            : "ln",
                    Arguments =
                        isWindows
                            ? $"/c mklink /J \"{linkPath}\" \"{targetPath}\""
                            : $"-s \"{targetPath}\" \"{linkPath}\"",
                    CreateNoWindow = true,
                    RedirectStandardError = true,
                    RedirectStandardOutput = true,
                    UseShellExecute = false
                };

            try
            {
                using Process process =
                    Process.Start(startInfo);

                if (process == null)
                {
                    failure =
                        "The link command could not be started.";

                    return false;
                }

                process.WaitForExit();

                string output =
                    process.StandardOutput.ReadToEnd();

                string error =
                    process.StandardError.ReadToEnd();

                if (process.ExitCode == 0)
                {
                    failure = string.Empty;

                    return true;
                }

                failure =
                    string.IsNullOrWhiteSpace(error)
                        ? output
                        : error;

                return false;
            }
            catch (Exception exception)
            {
                failure = exception.Message;

                return false;
            }
        }


        private void DeleteLinkIfPresent()
        {
            if (string.IsNullOrWhiteSpace(_linkPath))
                return;

            try
            {
                if (Directory.Exists(_linkPath))
                {
                    Directory.Delete(
                        _linkPath,
                        recursive: false);
                }
                else if (File.Exists(_linkPath))
                {
                    File.Delete(_linkPath);
                }
            }
            catch
            {
                // The isolated test root cleanup below is the final fallback.
            }
        }
    }
}
