package run

import (
	"os"
	"path/filepath"
	"sort"
	"testing"

	"github.com/stretchr/testify/require"
	"github.com/tidbcloud/AgentTeam/internal/workflow/plan"
)

func TestDecodeArtifactIndexesEveryCompilerGolden(t *testing.T) {
	t.Parallel()
	files, err := filepath.Glob(filepath.Join("..", "plan", "testdata", "ir", "*.json"))
	require.NoError(t, err)
	require.NotEmpty(t, files)
	for _, file := range files {
		t.Run(filepath.Base(file), func(t *testing.T) {
			raw, readErr := os.ReadFile(file)
			require.NoError(t, readErr)
			graph, decodeErr := DecodeArtifact(raw)
			require.NoError(t, decodeErr)
			require.Contains(t, []string{
				plan.ArtifactVersionV1, plan.ArtifactVersionV2, plan.ArtifactVersionV3,
				plan.ArtifactVersionV4, plan.ArtifactVersionV5,
			}, graph.Artifact.ArtifactVersion)
			for index := range graph.Artifact.Nodes {
				node := &graph.Artifact.Nodes[index]
				indexed, ok := graph.Node(node.ID)
				require.True(t, ok)
				require.Same(t, node, indexed)
				for _, port := range node.OutputPorts {
					edges := graph.Outgoing(node.ID, port.Name)
					ids := make([]string, len(edges))
					for edgeIndex, edge := range edges {
						ids[edgeIndex] = edge.ID
					}
					require.True(t, sort.StringsAreSorted(ids))
				}
			}
		})
	}
}

func TestCompilerGoldensCoverEveryRuntimeOpcode(t *testing.T) {
	t.Parallel()
	want := map[plan.ArtifactOpcode]bool{
		plan.OpcodeSendMessageSent: true, plan.OpcodeSendMessageCallback: true,
		plan.OpcodeSetVariable: true, plan.OpcodeWait: true, plan.OpcodeRequestApproval: true,
		plan.OpcodeSwitchFirstMatch: true, plan.OpcodeSwitchAllMatch: true,
		plan.OpcodeFork: true, plan.OpcodeJoinAll: true, plan.OpcodeJoinAny: true,
		plan.OpcodeLoop: true, plan.OpcodeEndSucceeded: true, plan.OpcodeEndFailed: true,
		plan.OpcodeFailPlan: true,
	}
	seen := map[plan.ArtifactOpcode]bool{}
	files, err := filepath.Glob(filepath.Join("..", "plan", "testdata", "ir", "*.json"))
	require.NoError(t, err)
	for _, file := range files {
		raw, readErr := os.ReadFile(file)
		require.NoError(t, readErr)
		graph, decodeErr := DecodeArtifact(raw)
		require.NoError(t, decodeErr)
		for _, node := range graph.Artifact.Nodes {
			seen[node.Opcode] = true
		}
	}
	artifact, diagnostics, err := plan.Compile([]byte(implicitFailureRuntimePlan))
	require.NoError(t, err)
	require.False(t, plan.HasErrors(diagnostics), "diagnostics: %#v", diagnostics)
	for _, node := range artifact.Nodes {
		seen[node.Opcode] = true
	}
	require.Equal(t, want, seen)
}

func TestDecodeArtifactRejectsSourceAndTamper(t *testing.T) {
	t.Parallel()
	_, err := DecodeArtifact([]byte("apiVersion: loop.plan/v1"))
	require.Error(t, err)
	raw := readGolden(t, "set_variable.json")
	raw[len(raw)-2] ^= 1
	_, err = DecodeArtifact(raw)
	require.Error(t, err)
}

func readGolden(t *testing.T, name string) []byte {
	t.Helper()
	raw, err := os.ReadFile(filepath.Join("..", "plan", "testdata", "ir", name))
	require.NoError(t, err)
	return raw
}

func goldenKernel(t *testing.T, name string) (*Kernel, *Graph) {
	t.Helper()
	graph, err := DecodeArtifact(readGolden(t, name))
	require.NoError(t, err)
	kernel, err := NewKernel(graph)
	require.NoError(t, err)
	return kernel, graph
}
