using System.Diagnostics;
using Avalonia.Threading;
using LanMountainDesktop.Launcher.Models;
using LanMountainDesktop.Launcher.Resources;
using LanMountainDesktop.Launcher.Startup;
using LanMountainDesktop.Launcher.Views;
using LanMountainDesktop.Shared.Contracts.Launcher;
using LanMountainDesktop.Shared.IPC;
using LanMountainDesktop.Shared.IPC.Abstractions.Services;

namespace LanMountainDesktop.Launcher.Services;

internal sealed partial class LauncherFlowCoordinator
{
    private async Task<HostLaunchOutcome> LaunchHostWithIpcAsync(bool forceDirectMode = false, string? retryTag = null)
    {
        var resolution = _deploymentLocator.ResolveHostExecutable(_context);
        if (!resolution.Success || string.IsNullOrWhiteSpace(resolution.ResolvedHostPath))
        {
            var (errorResult, selectedPath) = await ShowHostNotFoundErrorAsync().ConfigureAwait(false);
            if (errorResult == ErrorWindowResult.Retry)
            {
                if (!string.IsNullOrWhiteSpace(selectedPath) && File.Exists(selectedPath))
                {
                    return await LaunchHostWithExplicitPathAsync(selectedPath, forceDirectMode, retryTag).ConfigureAwait(false);
                }

                return await LaunchHostWithIpcAsync(forceDirectMode, retryTag).ConfigureAwait(false);
            }

            return HostLaunchOutcome.FromResult(BuildResult(
                success: false,
                stage: "launchHost",
                code: "host_not_found",
                message: "LanMountainDesktop host executable was not found.",
                details: BuildResolutionDetails(resolution, null, null, "resolve")));
        }

        return await LaunchHostWithResolvedPathAsync(resolution, forceDirectMode, retryTag).ConfigureAwait(false);
    }

    private Task<HostLaunchOutcome> LaunchHostWithExplicitPathAsync(string hostPath, bool forceDirectMode, string? retryTag)
    {
        var resolution = new HostResolutionResult
        {
            Success = true,
            ResolvedHostPath = Path.GetFullPath(hostPath),
            ResolutionSource = "user_selected_path",
            AppRoot = _deploymentLocator.GetAppRoot(),
            ExplicitAppRoot = Path.GetDirectoryName(hostPath),
            SearchedPaths = [Path.GetFullPath(hostPath)]
        };

        return LaunchHostWithResolvedPathAsync(resolution, forceDirectMode, retryTag);
    }

    internal static LauncherResult? ValidateDotNetRuntimePrerequisite(
        HostLaunchPlan plan,
        HostResolutionResult resolution,
        DotNetRuntimeProbeOptions? probeOptions = null)
    {
        ArgumentNullException.ThrowIfNull(plan);
        ArgumentNullException.ThrowIfNull(resolution);

        if (!DotNetRuntimeProbe.IsFrameworkDependentWindowsApp(plan.HostPath))
        {
            return null;
        }

        var runtime = DotNetRuntimeProbe.Probe(probeOptions);
        Logger.Info(
            $"Runtime prerequisite check completed. Available={runtime.IsAvailable}; " +
            $"Architecture={runtime.Architecture}; Message='{runtime.Message}'.");

        if (runtime.IsAvailable)
        {
            return null;
        }

        var details = BuildResolutionDetails(resolution, null, null, "runtime");
        foreach (var pair in runtime.ToDetails())
        {
            details[pair.Key] = pair.Value;
        }

        return BuildResult(
            success: false,
            stage: "launchHost",
            code: "dotnet_runtime_missing",
            message: ".NET 10 Desktop Runtime is required before LanMountainDesktop can start.",
            details: details,
            errorMessage: runtime.Message);
    }

    private async Task<HostLaunchOutcome> LaunchHostWithResolvedPathAsync(
        HostResolutionResult resolution,
        bool forceDirectMode,
        string? retryTag)
    {
        var dataRoot = _dataLocationResolver.ResolveDataRoot();
        var plan = HostLaunchPlanBuilder.Build(_context, _deploymentLocator, resolution, dataRoot);
        var prerequisiteFailure = ValidateDotNetRuntimePrerequisite(plan, resolution);
        if (prerequisiteFailure is not null)
        {
            return HostLaunchOutcome.FromResult(prerequisiteFailure);
        }

        var hostPath = plan.HostPath;
        if (OperatingSystem.IsLinux() || OperatingSystem.IsMacOS())
        {
            EnsureExecutable(hostPath);
        }

        var primaryMode = HostStartMode.Direct;
        var fallbackMode = !forceDirectMode && OperatingSystem.IsWindows()
            ? HostStartMode.ShellExecute
            : (HostStartMode?)null;

        var firstAttempt = await StartHostProcessAsync(plan, primaryMode, retryTag).ConfigureAwait(false);
        if (firstAttempt.ProcessCreated && firstAttempt.Process is not null)
        {
            var firstDetails = BuildResolutionDetails(resolution, firstAttempt, null, null);
            return HostLaunchOutcome.FromProcess(
                firstAttempt.Process,
                BuildResult(true, "launchHost", "ok", "Host launched.", firstDetails),
                firstDetails);
        }

        if (fallbackMode is null)
        {
            return BuildOutcomeFromAttempt(resolution, firstAttempt, null);
        }

        Logger.Warn(
            $"Primary host start attempt failed. Retrying with fallback mode '{fallbackMode}'. " +
            $"FailureReason='{firstAttempt.FailureReason ?? "unknown"}'; ExitCode='{firstAttempt.ExitCode?.ToString() ?? "<none>"}'.");

        var secondAttempt = await StartHostProcessAsync(plan, fallbackMode.Value, retryTag).ConfigureAwait(false);
        if (secondAttempt.ProcessCreated && secondAttempt.Process is not null)
        {
            var details = BuildResolutionDetails(resolution, firstAttempt, secondAttempt, null);
            return HostLaunchOutcome.FromProcess(
                secondAttempt.Process,
                BuildResult(true, "launchHost", "ok", "Host launched.", details),
                details);
        }

        return BuildOutcomeFromAttempt(resolution, secondAttempt, firstAttempt);
    }

    private static HostLaunchOutcome BuildOutcomeFromAttempt(
        HostResolutionResult resolution,
        HostStartAttempt finalAttempt,
        HostStartAttempt? previousAttempt)
    {
        var details = BuildResolutionDetails(
            resolution,
            previousAttempt ?? finalAttempt,
            previousAttempt is null ? null : finalAttempt,
            !finalAttempt.ProcessCreated
                ? "start"
                : finalAttempt.ExitCode is int finalExitCode && HostActivationPolicy.IsFailedActivationExitCode(finalExitCode)
                    ? "activation"
                    : "early-exit");

        if (!finalAttempt.ProcessCreated)
        {
            return HostLaunchOutcome.FromResult(BuildResult(
                false,
                "launchHost",
                "host_start_failed",
                $"Failed to start host using start mode '{finalAttempt.StartMode}'.",
                details));
        }

        if (finalAttempt.ExitCode is not null && HostActivationPolicy.IsSuccessfulActivationExitCode(finalAttempt.ExitCode.Value))
        {
            return HostLaunchOutcome.FromImmediateResult(BuildResult(
                true,
                "launch",
                "activation_redirected",
                "Launcher activation was redirected to the existing desktop instance.",
                details));
        }

        if (finalAttempt.ExitCode is not null && HostActivationPolicy.IsFailedActivationExitCode(finalAttempt.ExitCode.Value))
        {
            return HostLaunchOutcome.FromResult(BuildResult(
                false,
                "launch",
                "activation_failed",
                $"Host activation handshake failed using start mode '{finalAttempt.StartMode}'.",
                details));
        }

        return HostLaunchOutcome.FromResult(BuildResult(
            false,
            "launchHost",
            "host_exited_early",
            $"Host exited early using start mode '{finalAttempt.StartMode}'.",
            details));
    }

    private async Task<HostStartAttempt> StartHostProcessAsync(
        HostLaunchPlan plan,
        HostStartMode startMode,
        string? retryTag)
    {
        var startInfo = new ProcessStartInfo
        {
            FileName = plan.HostPath,
            WorkingDirectory = plan.WorkingDirectory,
            UseShellExecute = startMode == HostStartMode.ShellExecute
        };

        if (startMode == HostStartMode.Direct)
        {
            foreach (var argument in plan.Arguments)
            {
                startInfo.ArgumentList.Add(argument);
            }

            foreach (var pair in plan.EnvironmentVariables)
            {
                startInfo.EnvironmentVariables[pair.Key] = pair.Value;
            }
        }
        else
        {
            startInfo.Arguments = HostLaunchPlanBuilder.FormatArgumentsForLog(plan.Arguments);
        }

        try
        {
            var process = Process.Start(startInfo);
            Logger.Info(
                $"Host launch requested. Mode='{startMode}'; RetryTag='{retryTag ?? "<none>"}'; Path='{plan.HostPath}'; " +
                $"PackageRoot='{plan.PackageRoot}'; WorkingDir='{plan.WorkingDirectory}'; Pid={(process is null ? -1 : process.Id)}; " +
                $"Args='{HostLaunchPlanBuilder.FormatArgumentsForLog(plan.Arguments)}'.");

            if (process is null)
            {
                return HostStartAttempt.StartFailed(startMode, "process_start_returned_null", plan);
            }

            await Task.Yield();
            return HostStartAttempt.Started(startMode, process, plan);
        }
        catch (Exception ex)
        {
            Logger.Error($"Host start failed. Mode='{startMode}'.", ex);
            return HostStartAttempt.StartFailed(startMode, ex.GetType().Name, plan);
        }
    }

    private static Dictionary<string, string> BuildResolutionDetails(
        HostResolutionResult resolution,
        HostStartAttempt? firstAttempt,
        HostStartAttempt? secondAttempt,
        string? failureStage)
    {
        var details = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
        {
            ["resolvedAppRoot"] = resolution.AppRoot,
            ["explicitAppRoot"] = resolution.ExplicitAppRoot ?? string.Empty,
            ["resolvedHostPath"] = resolution.ResolvedHostPath ?? string.Empty,
            ["resolutionSource"] = resolution.ResolutionSource ?? string.Empty,
            ["devModeConfigIgnored"] = resolution.DevModeConfigIgnored.ToString(),
            ["searchedPaths"] = string.Join(" | ", resolution.SearchedPaths),
            ["failureStage"] = failureStage ?? string.Empty
        };

        if (firstAttempt is not null)
        {
            details["startMode"] = firstAttempt.StartMode.ToString();
            details["processCreated"] = firstAttempt.ProcessCreated.ToString();
            details["hostPid"] = firstAttempt.ProcessId?.ToString() ?? string.Empty;
            details["packageRoot"] = firstAttempt.PackageRoot ?? string.Empty;
            details["workingDirectory"] = firstAttempt.WorkingDirectory ?? string.Empty;
            details["arguments"] = firstAttempt.Arguments ?? string.Empty;
            details["firstAttemptFailureReason"] = firstAttempt.FailureReason ?? string.Empty;
            details["firstAttemptExitCode"] = firstAttempt.ExitCode?.ToString() ?? string.Empty;
        }

        if (secondAttempt is not null)
        {
            details["fallbackStartMode"] = secondAttempt.StartMode.ToString();
            details["fallbackProcessCreated"] = secondAttempt.ProcessCreated.ToString();
            details["fallbackHostPid"] = secondAttempt.ProcessId?.ToString() ?? string.Empty;
            details["fallbackPackageRoot"] = secondAttempt.PackageRoot ?? string.Empty;
            details["fallbackWorkingDirectory"] = secondAttempt.WorkingDirectory ?? string.Empty;
            details["fallbackArguments"] = secondAttempt.Arguments ?? string.Empty;
            details["fallbackFailureReason"] = secondAttempt.FailureReason ?? string.Empty;
            details["fallbackExitCode"] = secondAttempt.ExitCode?.ToString() ?? string.Empty;
        }

        return details;
    }
    private static void EnsureExecutable(string path)
    {
        if (OperatingSystem.IsWindows())
        {
            return;
        }

        try
        {
            var mode = File.GetUnixFileMode(path);
            mode |= UnixFileMode.UserExecute | UnixFileMode.GroupExecute | UnixFileMode.OtherExecute;
            File.SetUnixFileMode(path, mode);
        }
        catch
        {
        }
    }
}