using CliWrap;
using CliWrap.Buffered;

namespace InnovEnergy.Lib.Utils;

public class FileSystem
{
    
    private static readonly String ProcName = Utils.ExecutingProcessName;
    private static readonly String TempPath = Path.GetTempPath();

    private static Command Find { get; } = Cli.Wrap("find");
    private static Command Stat { get; } = Cli.Wrap("stat");
    private static Random  Rng  { get; } = new Random();

    public static FileSystem Local { get; } = new FileSystem();
    public static FileSystem OnHost(SshHost? host) => new FileSystem(host);

    private SshHost? Host { get; }

    private FileSystem(SshHost? host = null)
    {
        Host = host;
    }

    public Task<IReadOnlyList<String>> GetFiles(String path)
    {
        return GetFiles(path, FileType.RegularFile);
    }
    
    public Task<IReadOnlyList<String>> GetFilesRecursive(String path)
    {
        return GetFiles(path, FileType.RegularFile, maxDepth: Int16.MaxValue);
    }
    
    public Task<IReadOnlyList<String>> GetDirectories(String path)
    {
        return GetFiles(path, FileType.Directory);
    }

    public async Task<IReadOnlyList<String>> GetFiles(String path, FileType fileType, Int32 maxDepth = 1)
    {
        var result = await Find
                          .WithArguments($"{path} -maxdepth {maxDepth} -type {(Char)fileType}")
                          .OnHost(Host)
                          .ExecuteBufferedAsync();

        return result.StandardOutput.SplitLines();
    }
    
    public Boolean FileExists(String path)
    {
        var stat = StatF(path);

        return stat.ExitCode == 0 && stat.StandardOutput.Trim() == "regular file";
    }

    public Boolean DirectoryExists(String path)
    {
        var stat = StatF(path);

        return stat.ExitCode == 0 && stat.StandardOutput.Trim() == "directory";
    }

    private BufferedCommandResult StatF(String path)
    {
        return Stat
              .WithArguments($"-c %F {path}")
              .OnHost(Host)
              .ExecuteBufferedAsync()
              .Task
              .Result;
    }



    
    [Obsolete]
    public static Disposable<String> CreateTmpDir()
    {
        var path = GetRandomTempPath();

        Directory.CreateDirectory(path);

        void Dispose()
        {
            try
            {
                Directory.Delete(path, recursive: true);
            }
            catch
            {
                // ignored
            }
        }

        return Disposable.Create(path, Dispose);
    }

    
    [Obsolete]
    private static String GetRandomTempPath()
    {
        lock (Rng)
        {
            return $"{TempPath}{ProcName}.{Rng.Next():x8}";
        }
    }

    
    
    [Obsolete]
    public static Disposable<String> CreateTempFile(String fileName)
    {
        var dir = CreateTmpDir();
        var file = dir.Value.AppendPath(fileName);

        return Disposable.Create(file, dir.Dispose);
    }
}


public enum FileType : Byte
{
    BlockDevice     = (Byte) 'b',
    CharacterDevice = (Byte) 'c',
    Directory       = (Byte) 'd',
    Pipe            = (Byte) 'p',
    RegularFile     = (Byte) 'f',
    SymbolicLink    = (Byte) 'l',
}