using System.Diagnostics;
using YMhut.Box.Core.Logging;
using YMhut.Box.Core.Settings;

namespace YMhut.Box.Core.Tools;

public interface IExternalToolLaunchService
{
    Task<ReferenceToolExecutionResult> LaunchAsync(
        ExternalToolItem tool,
        string? launchPath = null,
        bool runAsAdministrator = false,
        bool confirmed = false,
        CancellationToken cancellationToken = default);

    Task<ReferenceToolExecutionResult> OpenDirectoryAsync(ExternalToolItem tool, CancellationToken cancellationToken = default);
}

public sealed class ExternalToolLaunchService(
    ILogService? logService = null,
    ISettingsService? settingsService = null) : IExternalToolLaunchService
{
    public async Task<ReferenceToolExecutionResult> LaunchAsync(
        ExternalToolItem tool,
        string? launchPath = null,
        bool runAsAdministrator = false,
        bool confirmed = false,
        CancellationToken cancellationToken = default)
    {
        var start = Stopwatch.StartNew();
        var target = string.IsNullOrWhiteSpace(launchPath) ? tool.LaunchPath : launchPath;
        var operation = runAsAdministrator ? "launch-admin" : "launch";
        if (tool.IsRisky && !confirmed)
        {
            await WriteToolLogAsync("Warning", tool.Id, operation, "confirmation-required", start.ElapsedMilliseconds, null, SafePath(target), cancellationToken).ConfigureAwait(false);
            return ReferenceToolExecutionResult.ConfirmationRequired("此工具需要二次确认后才能启动。", $"工具：{tool.Name}\n路径：{SafePath(target)}");
        }

        try
        {
            if (!File.Exists(target))
            {
                await WriteToolLogAsync("Error", tool.Id, operation, "failed", start.ElapsedMilliseconds, "file-not-found", SafePath(target), cancellationToken).ConfigureAwait(false);
                return ReferenceToolExecutionResult.Fail("启动文件不存在。", SafePath(target));
            }

            var extension = Path.GetExtension(target).ToLowerInvariant();
            var startInfo = BuildStartInfo(target, extension, runAsAdministrator || tool.RequiresAdministrator);
            Process.Start(startInfo);
            if (settingsService is not null)
            {
                await settingsService.RecordRecentToolAsync(tool.Id, cancellationToken).ConfigureAwait(false);
            }

            await WriteToolLogAsync("Information", tool.Id, operation, "success", start.ElapsedMilliseconds, null, SafePath(target), cancellationToken).ConfigureAwait(false);
            return ReferenceToolExecutionResult.Ok("工具已启动。", SafePath(target));
        }
        catch (Exception exception)
        {
            await WriteToolLogAsync("Error", tool.Id, operation, "failed", start.ElapsedMilliseconds, Sanitize(exception.Message), SafePath(target), cancellationToken).ConfigureAwait(false);
            return ReferenceToolExecutionResult.Fail("工具启动失败。", Sanitize(exception.Message));
        }
    }

    public async Task<ReferenceToolExecutionResult> OpenDirectoryAsync(ExternalToolItem tool, CancellationToken cancellationToken = default)
    {
        var start = Stopwatch.StartNew();
        try
        {
            if (!Directory.Exists(tool.ToolDirectory))
            {
                await WriteToolLogAsync("Error", tool.Id, "open-directory", "failed", start.ElapsedMilliseconds, "directory-not-found", SafePath(tool.ToolDirectory), cancellationToken).ConfigureAwait(false);
                return ReferenceToolExecutionResult.Fail("工具目录不存在。", SafePath(tool.ToolDirectory));
            }

            Process.Start(new ProcessStartInfo
            {
                FileName = tool.ToolDirectory,
                UseShellExecute = true
            });
            await WriteToolLogAsync("Information", tool.Id, "open-directory", "success", start.ElapsedMilliseconds, null, SafePath(tool.ToolDirectory), cancellationToken).ConfigureAwait(false);
            return ReferenceToolExecutionResult.Ok("目录已打开。", SafePath(tool.ToolDirectory));
        }
        catch (Exception exception)
        {
            await WriteToolLogAsync("Error", tool.Id, "open-directory", "failed", start.ElapsedMilliseconds, Sanitize(exception.Message), SafePath(tool.ToolDirectory), cancellationToken).ConfigureAwait(false);
            return ReferenceToolExecutionResult.Fail("打开目录失败。", Sanitize(exception.Message));
        }
    }

    private static ProcessStartInfo BuildStartInfo(string path, string extension, bool runAsAdministrator)
    {
        if (extension == ".ps1")
        {
            return new ProcessStartInfo
            {
                FileName = "powershell.exe",
                Arguments = $"-NoProfile -ExecutionPolicy Bypass -File \"{path}\"",
                WorkingDirectory = Path.GetDirectoryName(path) ?? Environment.CurrentDirectory,
                UseShellExecute = true,
                Verb = runAsAdministrator ? "runas" : string.Empty
            };
        }

        return new ProcessStartInfo
        {
            FileName = path,
            WorkingDirectory = Path.GetDirectoryName(path) ?? Environment.CurrentDirectory,
            UseShellExecute = true,
            Verb = runAsAdministrator ? "runas" : string.Empty
        };
    }

    private Task WriteToolLogAsync(
        string level,
        string toolId,
        string operation,
        string result,
        long elapsedMs,
        string? error,
        string? detail,
        CancellationToken cancellationToken)
    {
        var payload = $"toolId={toolId}; operation={operation}; result={result}; elapsedMs={elapsedMs}; error={error ?? ""}; detail={detail ?? ""}";
        return logService?.WriteAsync(level, "tool-run", $"External tool {result}: {operation}", payload, cancellationToken)
            ?? Task.CompletedTask;
    }

    private static string SafePath(string? path)
    {
        if (string.IsNullOrWhiteSpace(path))
        {
            return string.Empty;
        }

        var file = Path.GetFileName(path);
        var parent = Path.GetFileName(Path.GetDirectoryName(path) ?? string.Empty);
        return string.IsNullOrWhiteSpace(parent) ? file : Path.Combine("...", parent, file);
    }

    private static string Sanitize(string value)
        => value.Length > 260 ? value[..260] : value;
}
