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

#nullable enable

using System;
using System.Collections.Generic;
using System.IO;

namespace Microsoft.Extensions.Tools.Internal
{
    public class TemporaryDirectory : IDisposable
    {
        private readonly List<TemporaryCSharpProject> _projects = new List<TemporaryCSharpProject>();
        private readonly List<TemporaryDirectory> _subdirs = new List<TemporaryDirectory>();
        private readonly Dictionary<string, string> _files = new Dictionary<string, string>();
        private readonly TemporaryDirectory? _parent;

        public TemporaryDirectory()
        {
            Root = Path.Combine(ResolveLinks(Path.GetTempPath()), "dotnet-tool-tests", Guid.NewGuid().ToString("N"));
        }

        private TemporaryDirectory(string path, TemporaryDirectory parent)
        {
            _parent = parent;
            Root = path;
        }

        public TemporaryDirectory SubDir(string name)
        {
            var subdir = new TemporaryDirectory(Path.Combine(Root, name), this);
            _subdirs.Add(subdir);
            return subdir;
        }

        public string Root { get; }

        public TemporaryCSharpProject WithCSharpProject(string name, string sdk = "Microsoft.NET.Sdk")
        {
            var project = new TemporaryCSharpProject(name, this, sdk);
            return WithCSharpProject(project);
        }

        public TemporaryCSharpProject WithCSharpProject(TemporaryCSharpProject project)
        {
            _projects.Add(project);
            return project;
        }

        public TemporaryDirectory WithFile(string name, string contents = "")
        {
            _files[name] = contents;
            return this;
        }

        public TemporaryDirectory WithContentFile(string name)
        {
            using (var stream = File.OpenRead(Path.Combine("TestContent", $"{name}.txt")))
            using (var streamReader = new StreamReader(stream))
            {
                _files[name] = streamReader.ReadToEnd();
            }
            return this;
        }

        public TemporaryDirectory Up()
        {
            if (_parent == null)
            {
                throw new InvalidOperationException("This is the root directory");
            }
            return _parent;
        }

        public void Create()
        {
            Directory.CreateDirectory(Root);

            foreach (var dir in _subdirs)
            {
                dir.Create();
            }

            foreach (var project in _projects)
            {
                project.Create();
            }

            foreach (var file in _files)
            {
                CreateFile(file.Key, file.Value);
            }
        }

        public void CreateFile(string filename, string contents)
        {
            File.WriteAllText(Path.Combine(Root, filename), contents);
        }

        public void Dispose()
        {
            if (Root == null || !Directory.Exists(Root) || _parent != null)
            {
                return;
            }

            try
            {
                Directory.Delete(Root, recursive: true);
            }
            catch
            {
                Console.Error.WriteLine($"Test cleanup failed to delete '{Root}'");
            }
        }

        private static string ResolveLinks(string path)
        {
            if (!Directory.Exists(path))
            {
                return path;
            }

            var info = new DirectoryInfo(path);
            var segments = new List<string>();
            while (true)
            {
                if (info.LinkTarget is not null)
                {
                    // Found a link, use it until we reach root. Portions of resolved path may also be links.
                    info = new DirectoryInfo(info.LinkTarget);
                }

                segments.Add(info.Name);
                if (info.Parent is null)
                {
                    break;
                }

                info = info.Parent;
            }

            segments.Reverse();
            return Path.Combine(segments.ToArray());
        }
    }
}
