package cli_test

import (
	"context"
	"os"
	"path/filepath"
	"strings"
	"testing"

	"go.uber.org/zap"

	clipkg "iop/apps/node/internal/adapters/cli"
	"iop/apps/node/internal/adapters/cli/internal/testutil"
	noderuntime "iop/apps/node/internal/runtime"
	"iop/packages/go/config"
)

func TestCLIStartSkipsCodexExecPersistentStartup(t *testing.T) {
	testutil.RequireUnixShell(t)

	codex := writeFakeCodex(t, `#!/usr/bin/env sh
echo "codex should not be started during adapter startup" >&2
exit 42
`)
	cfg := config.CLIConf{
		Enabled: true,
		Profiles: map[string]config.CLIProfileConf{
			"codex": {
				Command:    codex,
				Args:       []string{"exec", "--dangerously-bypass-approvals-and-sandbox"},
				Persistent: true,
				Mode:       "codex-exec",
				ResumeArgs: []string{"exec", "resume", "--json"},
			},
		},
	}

	c := clipkg.New(cfg, zap.NewNop())
	if err := c.Start(context.Background()); err != nil {
		t.Fatalf("Start should not invoke codex exec: %v", err)
	}
}

func TestCLIExecuteCodexExecPersistentResumesLogicalSession(t *testing.T) {
	testutil.RequireUnixShell(t)

	codex := writeFakeCodex(t, `#!/usr/bin/env sh
if [ "$1" != "exec" ]; then
  echo "unexpected command: $*" >&2
  exit 2
fi

if [ "$2" = "resume" ]; then
  shift 2
  for arg in "$@"; do
    if [ "$arg" = "--color" ]; then
      echo "resume received unsupported --color option" >&2
      exit 12
    fi
  done

  session=""
  prompt=""
  for arg in "$@"; do
    case "$arg" in
      --*) continue ;;
    esac
    if [ -z "$session" ]; then
      session="$arg"
    else
      prompt="$arg"
    fi
  done

  printf "session id: %s\n" "$session" >&2
  printf "reply:resume:%s:%s\n" "$session" "$prompt"
  exit 0
fi

last=""
for arg in "$@"; do
  last="$arg"
done
printf "session id: codex-session-1\n" >&2
printf "reply:first:%s\n" "$last"
`)
	cfg := config.CLIConf{
		Enabled: true,
		Profiles: map[string]config.CLIProfileConf{
			"codex": {
				Command: codex,
				Args: []string{
					"exec",
					"--dangerously-bypass-approvals-and-sandbox",
					"--color",
					"never",
					"--skip-git-repo-check",
				},
				Mode:       "codex-exec",
				ResumeArgs: []string{"exec", "resume", "--json"},
			},
		},
	}

	c := clipkg.New(cfg, zap.NewNop())
	ctx := context.Background()

	first := &testutil.FakeSink{}
	if err := c.Execute(ctx, noderuntime.ExecutionSpec{
		RunID:     "run-1",
		Target:    "codex",
		SessionID: "session-a",
		Input:     map[string]any{"prompt": "first"},
	}, first); err != nil {
		t.Fatalf("first execute: %v", err)
	}
	if combined := testutil.CollectDeltas(first.Events()); !strings.Contains(combined, "reply:first:first") {
		t.Fatalf("first execute output: %q", combined)
	}

	second := &testutil.FakeSink{}
	if err := c.Execute(ctx, noderuntime.ExecutionSpec{
		RunID:     "run-2",
		Target:    "codex",
		SessionID: "session-a",
		Input:     map[string]any{"prompt": "second"},
	}, second); err != nil {
		t.Fatalf("second execute: %v", err)
	}
	if combined := testutil.CollectDeltas(second.Events()); !strings.Contains(combined, "reply:resume:codex-session-1:second") {
		t.Fatalf("second execute output: %q", combined)
	}
}

func TestCLIExecuteCodexExecJSONFormatStreamsAgentMessageAndResumes(t *testing.T) {
	testutil.RequireUnixShell(t)

	codex := writeFakeCodex(t, `#!/usr/bin/env sh
if [ "$1" != "exec" ]; then
  echo "unexpected command: $*" >&2
  exit 2
fi

if [ "$2" = "resume" ]; then
  shift 2
  session=""
  prompt=""
  for arg in "$@"; do
    case "$arg" in
      --*) continue ;;
    esac
    if [ -z "$session" ]; then
      session="$arg"
    else
      prompt="$arg"
    fi
  done
  printf '{"type":"thread.started","thread_id":"%s"}\n' "$session"
  printf '{"type":"item.completed","item":{"id":"item_0","type":"agent_message","text":"resume:%s:%s"}}\n' "$session" "$prompt"
  exit 0
fi

last=""
for arg in "$@"; do
  last="$arg"
done
printf '{"type":"thread.started","thread_id":"codex-session-json-1"}\n'
printf '{"type":"item.completed","item":{"id":"item_0","type":"agent_message","text":"first:%s"}}\n' "$last"
`)
	cfg := config.CLIConf{
		Enabled: true,
		Profiles: map[string]config.CLIProfileConf{
			"codex": {
				Command: codex,
				Args: []string{
					"exec",
					"--dangerously-bypass-approvals-and-sandbox",
					"--color",
					"never",
					"--skip-git-repo-check",
					"--json",
				},
				Persistent:   true,
				OutputFormat: "codex-json",
				Mode:         "codex-exec",
				ResumeArgs:   []string{"exec", "resume", "--json"},
			},
		},
	}

	c := clipkg.New(cfg, zap.NewNop())
	ctx := context.Background()

	first := &testutil.FakeSink{}
	if err := c.Execute(ctx, noderuntime.ExecutionSpec{
		RunID:     "run-1",
		Target:    "codex",
		SessionID: "session-a",
		Input:     map[string]any{"prompt": "hello"},
	}, first); err != nil {
		t.Fatalf("first execute: %v", err)
	}
	if combined := testutil.CollectDeltas(first.Events()); combined != "first:hello" {
		t.Fatalf("first deltas: got %q want %q", combined, "first:hello")
	}

	second := &testutil.FakeSink{}
	if err := c.Execute(ctx, noderuntime.ExecutionSpec{
		RunID:     "run-2",
		Target:    "codex",
		SessionID: "session-a",
		Input:     map[string]any{"prompt": "again"},
	}, second); err != nil {
		t.Fatalf("second execute: %v", err)
	}
	if combined := testutil.CollectDeltas(second.Events()); combined != "resume:codex-session-json-1:again" {
		t.Fatalf("second deltas: got %q", combined)
	}
}

func TestCLIExecuteCodexExec_MissingResumeArgsErrors(t *testing.T) {
	testutil.RequireUnixShell(t)

	codex := writeFakeCodex(t, `#!/usr/bin/env sh
echo "should not be called when resume_args is missing" >&2
exit 42
`)
	cfg := config.CLIConf{
		Enabled: true,
		Profiles: map[string]config.CLIProfileConf{
			"codex": {
				Command:    codex,
				Args:       []string{"exec", "--json"},
				Mode:       "codex-exec",
				ResumeArgs: nil,
			},
		},
	}

	c := clipkg.New(cfg, zap.NewNop())
	ctx := context.Background()
	sink := &testutil.FakeSink{}
	err := c.Execute(ctx, noderuntime.ExecutionSpec{
		RunID:     "run-1",
		Target:    "codex",
		SessionID: "session-b",
		Input:     map[string]any{"prompt": "test"},
	}, sink)
	if err == nil {
		t.Fatalf("expected error due to missing resume_args, got nil")
	}
	if !strings.Contains(err.Error(), "resume_args") {
		t.Fatalf("expected error to mention resume_args, got: %v", err)
	}
}

func writeFakeCodex(t *testing.T, script string) string {
	t.Helper()

	dir := t.TempDir()
	path := filepath.Join(dir, "codex")
	if err := os.WriteFile(path, []byte(script), 0o755); err != nil {
		t.Fatalf("write fake codex: %v", err)
	}
	return path
}
