package cli_test

import (
	"fmt"
	"runtime"
	"testing"

	"github.com/ory/dockertest/v3"
	"github.com/ory/dockertest/v3/docker"

	"github.com/coder/coder/v2/agent"
	"github.com/coder/coder/v2/agent/agentcontainers"
	"github.com/coder/coder/v2/agent/agenttest"
	"github.com/coder/coder/v2/cli/clitest"
	"github.com/coder/coder/v2/coderd/coderdtest"
	"github.com/coder/coder/v2/pty/ptytest"
	"github.com/coder/coder/v2/testutil"

	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
)

func TestExpRpty(t *testing.T) {
	t.Parallel()

	t.Run("OK", func(t *testing.T) {
		t.Parallel()

		client, workspace, agentToken := setupWorkspaceForAgent(t)
		inv, root := clitest.New(t, "exp", "rpty", workspace.Name)
		clitest.SetupConfig(t, client, root)
		pty := ptytest.New(t).Attach(inv)

		ctx := testutil.Context(t, testutil.WaitLong)

		cmdDone := tGo(t, func() {
			err := inv.WithContext(ctx).Run()
			assert.NoError(t, err)
		})

		_ = agenttest.New(t, client.URL, agentToken)
		_ = coderdtest.NewWorkspaceAgentWaiter(t, client, workspace.ID).Wait()

		pty.ExpectMatch(fmt.Sprintf("Connected to %s", workspace.Name))
		pty.WriteLine("exit")
		<-cmdDone
	})

	t.Run("NotFound", func(t *testing.T) {
		t.Parallel()

		client, _, _ := setupWorkspaceForAgent(t)
		inv, root := clitest.New(t, "exp", "rpty", "not-found")
		clitest.SetupConfig(t, client, root)

		ctx := testutil.Context(t, testutil.WaitShort)
		err := inv.WithContext(ctx).Run()
		require.ErrorContains(t, err, "not found")
	})

	t.Run("Container", func(t *testing.T) {
		t.Parallel()
		// Skip this test on non-Linux platforms since it requires Docker
		if runtime.GOOS != "linux" {
			t.Skip("Skipping test on non-Linux platform")
		}

		client, workspace, agentToken := setupWorkspaceForAgent(t)
		ctx := testutil.Context(t, testutil.WaitLong)
		pool, err := dockertest.NewPool("")
		require.NoError(t, err, "Could not connect to docker")
		ct, err := pool.RunWithOptions(&dockertest.RunOptions{
			Repository: "busybox",
			Tag:        "latest",
			Cmd:        []string{"sleep", "infnity"},
		}, func(config *docker.HostConfig) {
			config.AutoRemove = true
			config.RestartPolicy = docker.RestartPolicy{Name: "no"}
		})
		require.NoError(t, err, "Could not start container")
		// Wait for container to start
		require.Eventually(t, func() bool {
			ct, ok := pool.ContainerByName(ct.Container.Name)
			return ok && ct.Container.State.Running
		}, testutil.WaitShort, testutil.IntervalSlow, "Container did not start in time")
		t.Cleanup(func() {
			err := pool.Purge(ct)
			require.NoError(t, err, "Could not stop container")
		})

		_ = agenttest.New(t, client.URL, agentToken, func(o *agent.Options) {
			o.ExperimentalDevcontainersEnabled = true
			o.ContainerLister = agentcontainers.NewDocker(o.Execer)
		})
		_ = coderdtest.NewWorkspaceAgentWaiter(t, client, workspace.ID).Wait()

		inv, root := clitest.New(t, "exp", "rpty", workspace.Name, "-c", ct.Container.ID)
		clitest.SetupConfig(t, client, root)
		pty := ptytest.New(t).Attach(inv)

		cmdDone := tGo(t, func() {
			err := inv.WithContext(ctx).Run()
			assert.NoError(t, err)
		})

		pty.ExpectMatch(fmt.Sprintf("Connected to %s", workspace.Name))
		pty.ExpectMatch("Reconnect ID: ")
		pty.ExpectMatch(" #")
		pty.WriteLine("hostname")
		pty.ExpectMatch(ct.Container.Config.Hostname)
		pty.WriteLine("exit")
		<-cmdDone
	})
}