package runtimeconfig

import (
	"context"
	"fmt"
	"io"
	"os/exec"
	"strings"
	"time"
)

// CodexAppServerAvailable reports whether the Codex binary exposes the
// long-lived app-server entrypoint used by the daemon.
var CodexAppServerAvailable = func(binary string) bool {
	binary = strings.TrimSpace(binary)
	if binary == "" {
		binary = "codex"
	}

	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel()

	cmd := RuntimeCommandContext(ctx, TypeCodex, binary, []string{"app-server", "--help"})
	ConfigureProbeCommand(cmd)
	cmd.Env = runtimeProbeEnv()
	cmd.Stdout = io.Discard
	cmd.Stderr = io.Discard
	return cmd.Run() == nil
}

// KimiACPAvailable reports whether the Kimi binary exposes the public ACP
// stdio transport used by the daemon.
var KimiACPAvailable = func(binary string) bool {
	binary = strings.TrimSpace(binary)
	if binary == "" {
		binary = "kimi"
	}

	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel()

	cmd := RuntimeCommandContext(ctx, TypeKimi, binary, []string{"acp", "--help"})
	ConfigureProbeCommand(cmd)
	cmd.Env = runtimeProbeEnv()
	cmd.Stdout = io.Discard
	cmd.Stderr = io.Discard
	return cmd.Run() == nil
}

// PiRPCAvailable reports whether the pi binary exposes the long-lived RPC
// stdio mode used by the daemon.
var PiRPCAvailable = func(binary string) bool {
	binary = strings.TrimSpace(binary)
	if binary == "" {
		binary = "pi"
	}

	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel()

	command, args := RuntimeCommand(TypePi, binary, []string{"--mode", "rpc"})
	cmd := exec.CommandContext(ctx, command, args...)
	ConfigureProbeCommand(cmd)
	cmd.Env = runtimeProbeEnv()
	cmd.Stdout = io.Discard
	cmd.Stderr = io.Discard
	return cmd.Run() == nil
}

const (
	RuntimeProbeReasonBinaryMissing              = "binary_missing"
	RuntimeProbeReasonCapabilityMissing          = "capability_missing"
	RuntimeProbeReasonAgentTeamInstallIncomplete = "agentteam_install_incomplete"
	RuntimeProbeReasonVersionMismatch            = "version_mismatch"
)

// RuntimeProbeResult separates binary installation from AgentTeam-specific runtime
// capability checks. A runtime can be installed (Path resolved) but still not
// capable, for example an older Codex binary without the app-server entrypoint.
type RuntimeProbeResult struct {
	Path       string
	Installed  bool
	Capable    bool
	ReasonCode string
	Message    string
	Err        error
}

// RuntimeBinaryAvailable reports whether binary resolves to a local runtime
// candidate. It intentionally avoids runtime-specific launch probes; those run
// at launch time through ResolveRuntimeBinary so transient detection failures do
// not evict otherwise usable runtimes from the daemon registry.
func RuntimeBinaryAvailable(t RuntimeType, binary string) (string, bool) {
	probe := ProbeRuntimeCandidate(t, binary)
	return probe.Path, probe.Capable
}

// ProbeRuntimeCandidate resolves binary and reports whether the runtime can be
// registered as a launch candidate. Runtime-specific capability checks that may
// spawn heavier subprocesses belong in ProbeRuntimeBinary.
func ProbeRuntimeCandidate(t RuntimeType, binary string) RuntimeProbeResult {
	if shouldUseManagedOpenCode(t) {
		return ProbeManagedOpenCode()
	}
	binary = EffectiveRuntimeBinary(t, binary)
	resolver := DefaultRuntimeResolver()
	path, err := resolver.ResolveBinary(binary, resolver.RuntimeCandidatePaths(binary))
	if err != nil {
		return RuntimeProbeResult{
			Installed:  false,
			Capable:    false,
			ReasonCode: RuntimeProbeReasonBinaryMissing,
			Message:    fmt.Sprintf("binary %q was not found", binary),
			Err:        err,
		}
	}
	return RuntimeProbeResult{Path: path, Installed: true, Capable: true}
}

// ProbeRuntimeBinary resolves binary and verifies runtime-specific capability
// requirements while preserving the resolved path for diagnostics.
func ProbeRuntimeBinary(t RuntimeType, binary string) RuntimeProbeResult {
	effectiveType := EffectiveRuntimeType(t)
	probe := ProbeRuntimeCandidate(t, binary)
	if !probe.Installed || !probe.Capable {
		return probe
	}
	if effectiveType == TypeCodex && !CodexAppServerAvailable(probe.Path) {
		err := fmt.Errorf("codex binary %q does not expose app-server", probe.Path)
		return RuntimeProbeResult{
			Path:       probe.Path,
			Installed:  true,
			Capable:    false,
			ReasonCode: RuntimeProbeReasonCapabilityMissing,
			Message:    "Codex binary exists but the app-server entrypoint is unavailable",
			Err:        err,
		}
	}
	if effectiveType == TypeKimi && !KimiACPAvailable(probe.Path) {
		err := fmt.Errorf("kimi binary %q does not expose the ACP stdio entrypoint", probe.Path)
		return RuntimeProbeResult{
			Path:       probe.Path,
			Installed:  true,
			Capable:    false,
			ReasonCode: RuntimeProbeReasonCapabilityMissing,
			Message:    "Kimi binary exists but the public ACP entrypoint is unavailable",
			Err:        err,
		}
	}
	if effectiveType == TypePi && !PiRPCAvailable(probe.Path) {
		err := fmt.Errorf("pi binary %q does not expose RPC mode", probe.Path)
		return RuntimeProbeResult{
			Path:       probe.Path,
			Installed:  true,
			Capable:    false,
			ReasonCode: RuntimeProbeReasonCapabilityMissing,
			Message:    "pi binary exists but --mode rpc is unavailable",
			Err:        err,
		}
	}
	return probe
}

func shouldUseManagedOpenCode(t RuntimeType) bool {
	return NormalizeType(strings.TrimSpace(string(t))) == TypeBuiltin
}

// ResolveRuntimeBinary resolves binary to a concrete executable path for the
// runtime type and verifies any runtime-specific capability requirements.
func ResolveRuntimeBinary(t RuntimeType, binary string) (string, error) {
	probe := ProbeRuntimeBinary(t, binary)
	if probe.Capable {
		return probe.Path, nil
	}
	return probe.Path, probe.Err
}
