package config_test

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

	"iop/packages/go/config"
)

// validWorkspaceYAML is a baseline valid workspace catalog YAML used by
// multiple sub-tests. It uses a single node with one workspace defining read
// and list operations with bounded limits and a safe environment allowlist.
const validWorkspaceYAML = `
server:
  listen: "0.0.0.0:9090"
models:
  - id: "model-a"
    providers:
      prov-a: "model-a"
nodes:
  - id: "node-ws-01"
    alias: "mac-node"
    token: "token-ws-01"
    providers:
      - id: "prov-a"
        type: "ollama"
        category: "local_inference"
        models: ["model-a"]
        capacity: 2
    workspaces:
      - ref: "ws-project-root"
        platform: "darwin"
        root: "/Users/operator/projects/iop-workspace"
        operations:
          - "read"
          - "list"
        max_read_bytes: 1048576
        max_output_bytes: 4194304
        environment_allowlist:
          - "IOP_ENV"
`

// validWorkspaceWithCommandsYAML is a valid workspace catalog with command
// operations and command templates.
const validWorkspaceWithCommandsYAML = `
server:
  listen: "0.0.0.0:9090"
models:
  - id: "model-a"
    providers:
      prov-a: "model-a"
nodes:
  - id: "node-ws-cmd"
    alias: "mac-node-cmd"
    token: "token-ws-cmd"
    providers:
      - id: "prov-a"
        type: "ollama"
        category: "local_inference"
        models: ["model-a"]
        capacity: 2
    workspaces:
      - ref: "ws-cmd-workspace"
        platform: "darwin"
        root: "/Users/operator/projects/cmd-workspace"
        operations:
          - "read"
          - "list"
          - "write"
          - "delete"
          - "command"
        commands:
          - id: "list-files"
            executable: "/usr/bin/find"
            args:
              - "/Users/operator/projects/cmd-workspace"
              - "-name"
              - "*.go"
          - id: "read-file"
            executable: "/usr/bin/cat"
            args: []
        max_read_bytes: 2097152
        max_write_bytes: 1048576
        max_output_bytes: 8388608
        max_command_timeout_ms: 30000
        environment_allowlist:
          - "PATH"
          - "HOME"
`

// TestLoadEdgeWorkspaceCatalog verifies that valid workspace catalogs decode,
// normalize, and survive LoadEdge alongside ordinary node definitions.
func TestLoadEdgeWorkspaceCatalog(t *testing.T) {
	dir := t.TempDir()
	f := filepath.Join(dir, "edge.yaml")

	t.Run("single workspace with read/list operations loads", func(t *testing.T) {
		if err := os.WriteFile(f, []byte(validWorkspaceYAML), 0o600); err != nil {
			t.Fatalf("write yaml: %v", err)
		}
		cfg, err := config.LoadEdge(f)
		if err != nil {
			t.Fatalf("load: %v", err)
		}
		if len(cfg.Nodes) != 1 {
			t.Fatalf("expected 1 node, got %d", len(cfg.Nodes))
		}
		node := cfg.Nodes[0]
		if len(node.Workspaces) != 1 {
			t.Fatalf("expected 1 workspace, got %d", len(node.Workspaces))
		}
		ws := node.Workspaces[0]
		if ws.Ref != "ws-project-root" {
			t.Errorf("ref = %q, want ws-project-root", ws.Ref)
		}
		if ws.Platform != "darwin" {
			t.Errorf("platform = %q, want darwin", ws.Platform)
		}
		if ws.Root != "/Users/operator/projects/iop-workspace" {
			t.Errorf("root = %q, want /Users/operator/projects/iop-workspace", ws.Root)
		}
		if len(ws.Operations) != 2 {
			t.Fatalf("expected 2 operations, got %d", len(ws.Operations))
		}
		if ws.Operations[0] != config.WorkspaceOpRead {
			t.Errorf("operations[0] = %q, want read", ws.Operations[0])
		}
		if ws.Operations[1] != config.WorkspaceOpList {
			t.Errorf("operations[1] = %q, want list", ws.Operations[1])
		}
		if ws.MaxReadBytes != 1048576 {
			t.Errorf("max_read_bytes = %d, want 1048576", ws.MaxReadBytes)
		}
		if ws.MaxOutputBytes != 4194304 {
			t.Errorf("max_output_bytes = %d, want 4194304", ws.MaxOutputBytes)
		}
		if len(ws.EnvironmentAllowlist) != 1 || ws.EnvironmentAllowlist[0] != "IOP_ENV" {
			t.Errorf("environment_allowlist = %v, want [IOP_ENV]", ws.EnvironmentAllowlist)
		}
	})

	t.Run("linux workspace loads", func(t *testing.T) {
		yaml := strings.ReplaceAll(validWorkspaceYAML, `platform: "darwin"`, `platform: "linux"`)
		yaml = strings.ReplaceAll(yaml, `/Users/operator/projects/iop-workspace`, `/home/operator/projects/iop-workspace`)
		if err := os.WriteFile(f, []byte(yaml), 0o600); err != nil {
			t.Fatalf("write yaml: %v", err)
		}
		cfg, err := config.LoadEdge(f)
		if err != nil {
			t.Fatalf("LoadEdge: %v", err)
		}
		workspace := cfg.Nodes[0].Workspaces[0]
		if workspace.Platform != config.WorkspacePlatformLinux {
			t.Fatalf("platform = %q, want %q", workspace.Platform, config.WorkspacePlatformLinux)
		}
	})

	t.Run("workspace with command operations and templates loads", func(t *testing.T) {
		if err := os.WriteFile(f, []byte(validWorkspaceWithCommandsYAML), 0o600); err != nil {
			t.Fatalf("write yaml: %v", err)
		}
		cfg, err := config.LoadEdge(f)
		if err != nil {
			t.Fatalf("load: %v", err)
		}
		node := cfg.Nodes[0]
		ws := node.Workspaces[0]
		if ws.Ref != "ws-cmd-workspace" {
			t.Errorf("ref = %q, want ws-cmd-workspace", ws.Ref)
		}
		if len(ws.Operations) != 5 {
			t.Fatalf("expected 5 operations, got %d", len(ws.Operations))
		}
		if len(ws.Commands) != 2 {
			t.Fatalf("expected 2 commands, got %d", len(ws.Commands))
		}
		if ws.Commands[0].ID != "list-files" {
			t.Errorf("commands[0].id = %q, want list-files", ws.Commands[0].ID)
		}
		if ws.Commands[0].Executable != "/usr/bin/find" {
			t.Errorf("commands[0].executable = %q, want /usr/bin/find", ws.Commands[0].Executable)
		}
		if ws.Commands[1].ID != "read-file" {
			t.Errorf("commands[1].id = %q, want read-file", ws.Commands[1].ID)
		}
		if ws.MaxCommandTimeoutMS != 30000 {
			t.Errorf("max_command_timeout_ms = %d, want 30000", ws.MaxCommandTimeoutMS)
		}
	})

	t.Run("empty workspaces is backward-compatible", func(t *testing.T) {
		yaml := `
server:
  listen: "0.0.0.0:9090"
models:
  - id: "model-a"
    providers:
      prov-a: "model-a"
nodes:
  - id: "node-no-ws"
    alias: "no-workspace-node"
    token: "token-no-ws"
    providers:
      - id: "prov-a"
        type: "ollama"
        category: "local_inference"
        models: ["model-a"]
        capacity: 2
`
		if err := os.WriteFile(f, []byte(yaml), 0o600); err != nil {
			t.Fatalf("write yaml: %v", err)
		}
		cfg, err := config.LoadEdge(f)
		if err != nil {
			t.Fatalf("load: %v", err)
		}
		if len(cfg.Nodes[0].Workspaces) != 0 {
			t.Errorf("expected 0 workspaces, got %d", len(cfg.Nodes[0].Workspaces))
		}
	})

	t.Run("workspace ref is trimmed", func(t *testing.T) {
		yaml := `
server:
  listen: "0.0.0.0:9090"
models:
  - id: "model-a"
    providers:
      prov-a: "model-a"
nodes:
  - id: "node-ws-trim"
    alias: "trim-node"
    token: "token-trim"
    providers:
      - id: "prov-a"
        type: "ollama"
        category: "local_inference"
        models: ["model-a"]
        capacity: 2
    workspaces:
      - ref: "  ws-trimmed  "
        platform: "darwin"
        root: "/Users/operator/projects/trimmed"
        operations:
          - "read"
        max_read_bytes: 1024
`
		if err := os.WriteFile(f, []byte(yaml), 0o600); err != nil {
			t.Fatalf("write yaml: %v", err)
		}
		cfg, err := config.LoadEdge(f)
		if err != nil {
			t.Fatalf("load: %v", err)
		}
		if cfg.Nodes[0].Workspaces[0].Ref != "ws-trimmed" {
			t.Errorf("ref = %q, want ws-trimmed (trimmed)", cfg.Nodes[0].Workspaces[0].Ref)
		}
	})

	t.Run("workspace coexists with execution_presets", func(t *testing.T) {
		yaml := `
server:
  listen: "0.0.0.0:9090"
models:
  - id: "model-a"
    providers:
      prov-a: "model-a"
  - id: "model-plan"
    providers:
      prov-a: "model-plan"
  - id: "model-work"
    providers:
      prov-a: "model-work"
  - id: "model-review"
    providers:
      prov-a: "model-review"
execution_presets:
  - id: "preset-fixed-light"
    selector:
      model: "model-plan"
      options:
        reasoning_effort: "high"
    allowed_modes:
      - "light"
    routes:
      light:
        stages:
          - role: "plan"
            model: "model-plan"
            options:
              reasoning_effort: "high"
          - role: "work"
            model: "model-work"
          - role: "review"
            model: "model-review"
            options:
              reasoning_effort: "high"
    single_request:
      workspace_ref: "ws-single-request-ref"
      limits:
        wall_clock_ms: 1800000
        timeout_ms: 600000
        max_tool_iterations: 64
        max_output_bytes: 16777216
      stages:
        plan:
          model: "model-plan"
          options:
            reasoning_effort: "high"
        work:
          model: "model-work"
        review:
          model: "model-review"
          options:
            reasoning_effort: "high"
nodes:
  - id: "node-ws-and-preset"
    alias: "full-node"
    token: "token-full"
    providers:
      - id: "prov-a"
        type: "ollama"
        category: "local_inference"
        models: ["model-a", "model-plan", "model-work", "model-review"]
        capacity: 2
    workspaces:
      - ref: "ws-operator-root"
        platform: "darwin"
        root: "/Users/operator/projects/iop"
        operations:
          - "read"
          - "list"
        max_read_bytes: 1048576
        max_output_bytes: 4194304
`
		if err := os.WriteFile(f, []byte(yaml), 0o600); err != nil {
			t.Fatalf("write yaml: %v", err)
		}
		cfg, err := config.LoadEdge(f)
		if err != nil {
			t.Fatalf("load: %v", err)
		}
		if len(cfg.Nodes) != 1 {
			t.Fatalf("expected 1 node, got %d", len(cfg.Nodes))
		}
		if len(cfg.Nodes[0].Workspaces) != 1 {
			t.Fatalf("expected 1 workspace, got %d", len(cfg.Nodes[0].Workspaces))
		}
		if len(cfg.ExecutionPresets) != 1 {
			t.Fatalf("expected 1 preset, got %d", len(cfg.ExecutionPresets))
		}
	})
}

// TestLoadEdgeWorkspaceCatalogRejectsInvalid verifies that invalid workspace
// catalogs fail closed with descriptive errors covering all validation
// dimensions: ref, platform, root, operations, commands, environment, and
// numeric limits.
func TestLoadEdgeWorkspaceCatalogRejectsInvalid(t *testing.T) {
	dir := t.TempDir()
	f := filepath.Join(dir, "edge.yaml")

	baseNode := `
models:
  - id: "model-a"
    providers:
      prov-a: "model-a"
nodes:`

	t.Run("empty ref rejected", func(t *testing.T) {
		yaml := baseNode + `
  - id: "node-ws-empty-ref"
    alias: "empty-ref-node"
    token: "token-empty-ref"
    providers:
      - id: "prov-a"
        type: "ollama"
        category: "local_inference"
        models: ["model-a"]
        capacity: 2
    workspaces:
      - ref: "   "
        platform: "darwin"
        root: "/Users/operator/projects/test"
        operations:
          - "read"
        max_read_bytes: 1024
`
		if err := os.WriteFile(f, []byte(yaml), 0o600); err != nil {
			t.Fatalf("write yaml: %v", err)
		}
		_, err := config.LoadEdge(f)
		if err == nil {
			t.Fatal("expected error for empty ref")
		}
		if !strings.Contains(err.Error(), "ref must not be empty") {
			t.Fatalf("expected ref error, got %v", err)
		}
	})

	t.Run("duplicate ref within node rejected", func(t *testing.T) {
		yaml := baseNode + `
  - id: "node-ws-dup-ref"
    alias: "dup-ref-node"
    token: "token-dup-ref"
    providers:
      - id: "prov-a"
        type: "ollama"
        category: "local_inference"
        models: ["model-a"]
        capacity: 2
    workspaces:
      - ref: "ws-dup"
        platform: "darwin"
        root: "/Users/operator/projects/test1"
        operations:
          - "read"
        max_read_bytes: 1024
      - ref: "ws-dup"
        platform: "darwin"
        root: "/Users/operator/projects/test2"
        operations:
          - "list"
        max_read_bytes: 2048
        max_output_bytes: 2048
`
		if err := os.WriteFile(f, []byte(yaml), 0o600); err != nil {
			t.Fatalf("write yaml: %v", err)
		}
		_, err := config.LoadEdge(f)
		if err == nil {
			t.Fatal("expected error for duplicate ref within node")
		}
		if !strings.Contains(err.Error(), "duplicate ref") {
			t.Fatalf("expected duplicate ref error, got %v", err)
		}
	})

	t.Run("duplicate ref across nodes rejected", func(t *testing.T) {
		yaml := baseNode + `
  - id: "node-ws-dup-a"
    alias: "dup-a-node"
    token: "token-dup-a"
    providers:
      - id: "prov-a"
        type: "ollama"
        category: "local_inference"
        models: ["model-a"]
        capacity: 2
    workspaces:
      - ref: "ws-global-dup"
        platform: "darwin"
        root: "/Users/operator/projects/test-a"
        operations:
          - "read"
        max_read_bytes: 1024
  - id: "node-ws-dup-b"
    alias: "dup-b-node"
    token: "token-dup-b"
    providers:
      - id: "prov-b"
        type: "ollama"
        category: "local_inference"
        models: ["model-a"]
        capacity: 2
    workspaces:
      - ref: "ws-global-dup"
        platform: "darwin"
        root: "/Users/operator/projects/test-b"
        operations:
          - "list"
        max_read_bytes: 2048
        max_output_bytes: 2048
`
		if err := os.WriteFile(f, []byte(yaml), 0o600); err != nil {
			t.Fatalf("write yaml: %v", err)
		}
		_, err := config.LoadEdge(f)
		if err == nil {
			t.Fatal("expected error for duplicate ref across nodes")
		}
		if !strings.Contains(err.Error(), "duplicate workspace ref") {
			t.Fatalf("expected cross-node duplicate error, got %v", err)
		}
	})

	for _, platform := range []string{"windows", "plan9"} {
		t.Run("unsupported platform "+platform+" rejected", func(t *testing.T) {
			yaml := baseNode + `
  - id: "node-ws-bad-platform"
    alias: "bad-platform-node"
    token: "token-bad-platform"
    providers:
      - id: "prov-a"
        type: "ollama"
        category: "local_inference"
        models: ["model-a"]
        capacity: 2
    workspaces:
      - ref: "ws-unsupported"
        platform: "` + platform + `"
        root: "/home/operator/projects/test"
        operations:
          - "read"
        max_read_bytes: 1024
`
			if err := os.WriteFile(f, []byte(yaml), 0o600); err != nil {
				t.Fatalf("write yaml: %v", err)
			}
			_, err := config.LoadEdge(f)
			if err == nil {
				t.Fatalf("expected error for unsupported platform %q", platform)
			}
			if !strings.Contains(err.Error(), "unsupported workspace platform") {
				t.Fatalf("expected unsupported platform error, got %v", err)
			}
		})
	}

	t.Run("relative root path rejected", func(t *testing.T) {
		yaml := baseNode + `
  - id: "node-ws-rel-root"
    alias: "rel-root-node"
    token: "token-rel-root"
    providers:
      - id: "prov-a"
        type: "ollama"
        category: "local_inference"
        models: ["model-a"]
        capacity: 2
    workspaces:
      - ref: "ws-rel-root"
        platform: "darwin"
        root: "relative/path"
        operations:
          - "read"
        max_read_bytes: 1024
`
		if err := os.WriteFile(f, []byte(yaml), 0o600); err != nil {
			t.Fatalf("write yaml: %v", err)
		}
		_, err := config.LoadEdge(f)
		if err == nil {
			t.Fatal("expected error for relative root")
		}
		if !strings.Contains(err.Error(), "absolute path") {
			t.Fatalf("expected absolute path error, got %v", err)
		}
	})

	t.Run("root \"/\" rejected", func(t *testing.T) {
		yaml := baseNode + `
  - id: "node-ws-root-slash"
    alias: "root-slash-node"
    token: "token-root-slash"
    providers:
      - id: "prov-a"
        type: "ollama"
        category: "local_inference"
        models: ["model-a"]
        capacity: 2
    workspaces:
      - ref: "ws-root-slash"
        platform: "darwin"
        root: "/"
        operations:
          - "read"
        max_read_bytes: 1024
`
		if err := os.WriteFile(f, []byte(yaml), 0o600); err != nil {
			t.Fatalf("write yaml: %v", err)
		}
		_, err := config.LoadEdge(f)
		if err == nil {
			t.Fatal("expected error for root = /")
		}
		if !strings.Contains(err.Error(), `must not be "/"`) {
			t.Fatalf("expected root / error, got %v", err)
		}
	})

	t.Run("unclean root path rejected", func(t *testing.T) {
		yaml := baseNode + `
  - id: "node-ws-unclean"
    alias: "unclean-node"
    token: "token-unclean"
    providers:
      - id: "prov-a"
        type: "ollama"
        category: "local_inference"
        models: ["model-a"]
        capacity: 2
    workspaces:
      - ref: "ws-unclean"
        platform: "darwin"
        root: "/Users/operator/projects/../projects/test"
        operations:
          - "read"
        max_read_bytes: 1024
`
		if err := os.WriteFile(f, []byte(yaml), 0o600); err != nil {
			t.Fatalf("write yaml: %v", err)
		}
		_, err := config.LoadEdge(f)
		if err == nil {
			t.Fatal("expected error for unclean root")
		}
		if !strings.Contains(err.Error(), "must be clean") {
			t.Fatalf("expected clean path error, got %v", err)
		}
	})

	t.Run("empty operations rejected", func(t *testing.T) {
		yaml := baseNode + `
  - id: "node-ws-empty-ops"
    alias: "empty-ops-node"
    token: "token-empty-ops"
    providers:
      - id: "prov-a"
        type: "ollama"
        category: "local_inference"
        models: ["model-a"]
        capacity: 2
    workspaces:
      - ref: "ws-empty-ops"
        platform: "darwin"
        root: "/Users/operator/projects/test"
        operations: []
        max_read_bytes: 1024
`
		if err := os.WriteFile(f, []byte(yaml), 0o600); err != nil {
			t.Fatalf("write yaml: %v", err)
		}
		_, err := config.LoadEdge(f)
		if err == nil {
			t.Fatal("expected error for empty operations")
		}
		if !strings.Contains(err.Error(), "operations must not be empty") {
			t.Fatalf("expected empty operations error, got %v", err)
		}
	})

	t.Run("unknown operation rejected", func(t *testing.T) {
		yaml := baseNode + `
  - id: "node-ws-bad-op"
    alias: "bad-op-node"
    token: "token-bad-op"
    providers:
      - id: "prov-a"
        type: "ollama"
        category: "local_inference"
        models: ["model-a"]
        capacity: 2
    workspaces:
      - ref: "ws-bad-op"
        platform: "darwin"
        root: "/Users/operator/projects/test"
        operations:
          - "read"
          - "execute"
        max_read_bytes: 1024
`
		if err := os.WriteFile(f, []byte(yaml), 0o600); err != nil {
			t.Fatalf("write yaml: %v", err)
		}
		_, err := config.LoadEdge(f)
		if err == nil {
			t.Fatal("expected error for unknown operation")
		}
		if !strings.Contains(err.Error(), "unknown operation") {
			t.Fatalf("expected unknown operation error, got %v", err)
		}
	})

	t.Run("duplicate operation rejected", func(t *testing.T) {
		yaml := baseNode + `
  - id: "node-ws-dup-op"
    alias: "dup-op-node"
    token: "token-dup-op"
    providers:
      - id: "prov-a"
        type: "ollama"
        category: "local_inference"
        models: ["model-a"]
        capacity: 2
    workspaces:
      - ref: "ws-dup-op"
        platform: "darwin"
        root: "/Users/operator/projects/test"
        operations:
          - "read"
          - "read"
        max_read_bytes: 1024
`
		if err := os.WriteFile(f, []byte(yaml), 0o600); err != nil {
			t.Fatalf("write yaml: %v", err)
		}
		_, err := config.LoadEdge(f)
		if err == nil {
			t.Fatal("expected error for duplicate operation")
		}
		if !strings.Contains(err.Error(), "duplicate operation") {
			t.Fatalf("expected duplicate operation error, got %v", err)
		}
	})

	t.Run("commands present without command operation rejected", func(t *testing.T) {
		yaml := baseNode + `
  - id: "node-ws-cmd-without-op"
    alias: "cmd-without-op-node"
    token: "token-cmd-without-op"
    providers:
      - id: "prov-a"
        type: "ollama"
        category: "local_inference"
        models: ["model-a"]
        capacity: 2
    workspaces:
      - ref: "ws-cmd-without-op"
        platform: "darwin"
        root: "/Users/operator/projects/test"
        operations:
          - "read"
          - "list"
        commands:
          - id: "list-files"
            executable: "/usr/bin/find"
            args: []
        max_read_bytes: 1024
`
		if err := os.WriteFile(f, []byte(yaml), 0o600); err != nil {
			t.Fatalf("write yaml: %v", err)
		}
		_, err := config.LoadEdge(f)
		if err == nil {
			t.Fatal("expected error for commands without command operation")
		}
		if !strings.Contains(err.Error(), "commands must be empty") {
			t.Fatalf("expected commands must be empty error, got %v", err)
		}
	})

	t.Run("command operation without commands rejected", func(t *testing.T) {
		yaml := baseNode + `
  - id: "node-ws-cmd-op-no-cmds"
    alias: "cmd-op-no-cmds-node"
    token: "token-cmd-op-no-cmds"
    providers:
      - id: "prov-a"
        type: "ollama"
        category: "local_inference"
        models: ["model-a"]
        capacity: 2
    workspaces:
      - ref: "ws-cmd-op-no-cmds"
        platform: "darwin"
        root: "/Users/operator/projects/test"
        operations:
          - "read"
          - "command"
        max_read_bytes: 1024
`
		if err := os.WriteFile(f, []byte(yaml), 0o600); err != nil {
			t.Fatalf("write yaml: %v", err)
		}
		_, err := config.LoadEdge(f)
		if err == nil {
			t.Fatal("expected error for command operation without commands")
		}
		if !strings.Contains(err.Error(), "commands must not be empty") {
			t.Fatalf("expected commands must not be empty error, got %v", err)
		}
	})

	t.Run("duplicate command id rejected", func(t *testing.T) {
		yaml := baseNode + `
  - id: "node-ws-dup-cmd"
    alias: "dup-cmd-node"
    token: "token-dup-cmd"
    providers:
      - id: "prov-a"
        type: "ollama"
        category: "local_inference"
        models: ["model-a"]
        capacity: 2
    workspaces:
      - ref: "ws-dup-cmd"
        platform: "darwin"
        root: "/Users/operator/projects/test"
        operations:
          - "read"
          - "command"
        commands:
          - id: "list-files"
            executable: "/usr/bin/find"
            args: []
          - id: "list-files"
            executable: "/usr/bin/ls"
            args: []
        max_read_bytes: 1024
`
		if err := os.WriteFile(f, []byte(yaml), 0o600); err != nil {
			t.Fatalf("write yaml: %v", err)
		}
		_, err := config.LoadEdge(f)
		if err == nil {
			t.Fatal("expected error for duplicate command id")
		}
		if !strings.Contains(err.Error(), "duplicate command id") {
			t.Fatalf("expected duplicate command id error, got %v", err)
		}
	})

	t.Run("non-absolute command executable rejected", func(t *testing.T) {
		yaml := baseNode + `
  - id: "node-ws-rel-exe"
    alias: "rel-exe-node"
    token: "token-rel-exe"
    providers:
      - id: "prov-a"
        type: "ollama"
        category: "local_inference"
        models: ["model-a"]
        capacity: 2
    workspaces:
      - ref: "ws-rel-exe"
        platform: "darwin"
        root: "/Users/operator/projects/test"
        operations:
          - "read"
          - "command"
        commands:
          - id: "list-files"
            executable: "relative/path/to/cmd"
            args: []
        max_read_bytes: 1024
`
		if err := os.WriteFile(f, []byte(yaml), 0o600); err != nil {
			t.Fatalf("write yaml: %v", err)
		}
		_, err := config.LoadEdge(f)
		if err == nil {
			t.Fatal("expected error for relative command executable")
		}
		if !strings.Contains(err.Error(), "absolute path") {
			t.Fatalf("expected absolute path error for executable, got %v", err)
		}
	})

	t.Run("invalid environment variable name rejected", func(t *testing.T) {
		yaml := baseNode + `
  - id: "node-ws-bad-env"
    alias: "bad-env-node"
    token: "token-bad-env"
    providers:
      - id: "prov-a"
        type: "ollama"
        category: "local_inference"
        models: ["model-a"]
        capacity: 2
    workspaces:
      - ref: "ws-bad-env"
        platform: "darwin"
        root: "/Users/operator/projects/test"
        operations:
          - "read"
        max_read_bytes: 1024
        environment_allowlist:
          - "1INVALID"
`
		if err := os.WriteFile(f, []byte(yaml), 0o600); err != nil {
			t.Fatalf("write yaml: %v", err)
		}
		_, err := config.LoadEdge(f)
		if err == nil {
			t.Fatal("expected error for invalid env name")
		}
		if !strings.Contains(err.Error(), "portable environment variable name") {
			t.Fatalf("expected portable env name error, got %v", err)
		}
	})

	t.Run("duplicate environment variable name rejected", func(t *testing.T) {
		yaml := baseNode + `
  - id: "node-ws-dup-env"
    alias: "dup-env-node"
    token: "token-dup-env"
    providers:
      - id: "prov-a"
        type: "ollama"
        category: "local_inference"
        models: ["model-a"]
        capacity: 2
    workspaces:
      - ref: "ws-dup-env"
        platform: "darwin"
        root: "/Users/operator/projects/test"
        operations:
          - "read"
        max_read_bytes: 1024
        environment_allowlist:
          - "PATH"
          - "PATH"
`
		if err := os.WriteFile(f, []byte(yaml), 0o600); err != nil {
			t.Fatalf("write yaml: %v", err)
		}
		_, err := config.LoadEdge(f)
		if err == nil {
			t.Fatal("expected error for duplicate env name")
		}
		if !strings.Contains(err.Error(), "duplicate name") {
			t.Fatalf("expected duplicate name error, got %v", err)
		}
	})

	t.Run("max_read_bytes over 1GiB rejected", func(t *testing.T) {
		yaml := baseNode + `
  - id: "node-ws-over-read"
    alias: "over-read-node"
    token: "token-over-read"
    providers:
      - id: "prov-a"
        type: "ollama"
        category: "local_inference"
        models: ["model-a"]
        capacity: 2
    workspaces:
      - ref: "ws-over-read"
        platform: "darwin"
        root: "/Users/operator/projects/test"
        operations:
          - "read"
        max_read_bytes: 1073741825
`
		if err := os.WriteFile(f, []byte(yaml), 0o600); err != nil {
			t.Fatalf("write yaml: %v", err)
		}
		_, err := config.LoadEdge(f)
		if err == nil {
			t.Fatal("expected error for over-limit max_read_bytes")
		}
		if !strings.Contains(err.Error(), "max_read_bytes must be between") {
			t.Fatalf("expected max_read_bytes range error, got %v", err)
		}
	})

	t.Run("max_command_timeout_ms over 1 hour rejected", func(t *testing.T) {
		yaml := baseNode + `
  - id: "node-ws-over-timeout"
    alias: "over-timeout-node"
    token: "token-over-timeout"
    providers:
      - id: "prov-a"
        type: "ollama"
        category: "local_inference"
        models: ["model-a"]
        capacity: 2
    workspaces:
      - ref: "ws-over-timeout"
        platform: "darwin"
        root: "/Users/operator/projects/test"
        operations:
          - "read"
          - "command"
        commands:
          - id: "list-files"
            executable: "/usr/bin/find"
            args: []
        max_command_timeout_ms: 3600001
`
		if err := os.WriteFile(f, []byte(yaml), 0o600); err != nil {
			t.Fatalf("write yaml: %v", err)
		}
		_, err := config.LoadEdge(f)
		if err == nil {
			t.Fatal("expected error for over-limit timeout")
		}
		if !strings.Contains(err.Error(), "max_command_timeout_ms must be between") {
			t.Fatalf("expected timeout range error, got %v", err)
		}
	})

	for _, tc := range []struct {
		name       string
		operations string
		commands   string
		limits     string
		want       string
	}{
		{
			name:       "read without max_read_bytes rejected",
			operations: "          - \"read\"\n",
			want:       "max_read_bytes must be positive",
		},
		{
			name:       "list without max_output_bytes rejected",
			operations: "          - \"list\"\n",
			want:       "max_output_bytes must be positive",
		},
		{
			name:       "write without max_write_bytes rejected",
			operations: "          - \"write\"\n",
			want:       "max_write_bytes must be positive",
		},
		{
			name:       "command without max_output_bytes rejected",
			operations: "          - \"command\"\n",
			commands:   "        commands:\n          - id: \"list-files\"\n            executable: \"/usr/bin/find\"\n            args: []\n",
			limits:     "        max_command_timeout_ms: 1000\n",
			want:       "max_output_bytes must be positive",
		},
		{
			name:       "command without max_command_timeout_ms rejected",
			operations: "          - \"command\"\n",
			commands:   "        commands:\n          - id: \"list-files\"\n            executable: \"/usr/bin/find\"\n            args: []\n",
			limits:     "        max_output_bytes: 1024\n",
			want:       "max_command_timeout_ms must be positive",
		},
		{
			name:       "negative max_read_bytes rejected",
			operations: "          - \"read\"\n",
			limits:     "        max_read_bytes: -1\n",
			want:       "max_read_bytes must be between",
		},
		{
			name:       "negative max_write_bytes rejected",
			operations: "          - \"write\"\n",
			limits:     "        max_write_bytes: -1\n",
			want:       "max_write_bytes must be between",
		},
		{
			name:       "negative max_output_bytes rejected",
			operations: "          - \"list\"\n",
			limits:     "        max_output_bytes: -1\n",
			want:       "max_output_bytes must be between",
		},
		{
			name:       "negative max_command_timeout_ms rejected",
			operations: "          - \"command\"\n",
			commands:   "        commands:\n          - id: \"list-files\"\n            executable: \"/usr/bin/find\"\n            args: []\n",
			limits:     "        max_output_bytes: 1024\n        max_command_timeout_ms: -1\n",
			want:       "max_command_timeout_ms must be between",
		},
	} {
		t.Run(tc.name, func(t *testing.T) {
			yaml := baseNode + `
  - id: "node-ws-effective-bound"
    alias: "effective-bound-node"
    token: "token-effective-bound"
    providers:
      - id: "prov-a"
        type: "ollama"
        category: "local_inference"
        models: ["model-a"]
        capacity: 2
    workspaces:
      - ref: "ws-effective-bound"
        platform: "darwin"
        root: "/Users/operator/projects/test"
        operations:
` + tc.operations + tc.commands + tc.limits
			if err := os.WriteFile(f, []byte(yaml), 0o600); err != nil {
				t.Fatalf("write yaml: %v", err)
			}
			_, err := config.LoadEdge(f)
			if err == nil {
				t.Fatal("expected effective bound validation error")
			}
			if !strings.Contains(err.Error(), tc.want) {
				t.Fatalf("expected error containing %q, got %v", tc.want, err)
			}
		})
	}

	t.Run("boundary limit values load", func(t *testing.T) {
		yaml := baseNode + `
  - id: "node-ws-boundary"
    alias: "boundary-node"
    token: "token-boundary"
    providers:
      - id: "prov-a"
        type: "ollama"
        category: "local_inference"
        models: ["model-a"]
        capacity: 2
    workspaces:
      - ref: "ws-boundary"
        platform: "darwin"
        root: "/Users/operator/projects/test"
        operations:
          - "read"
          - "write"
          - "delete"
          - "command"
        commands:
          - id: "cmd-boundary"
            executable: "/usr/bin/cat"
            args: []
        max_read_bytes: 1
        max_write_bytes: 1073741824
        max_output_bytes: 1
        max_command_timeout_ms: 1
`
		if err := os.WriteFile(f, []byte(yaml), 0o600); err != nil {
			t.Fatalf("write yaml: %v", err)
		}
		cfg, err := config.LoadEdge(f)
		if err != nil {
			t.Fatalf("load: %v", err)
		}
		ws := cfg.Nodes[0].Workspaces[0]
		if ws.MaxReadBytes != 1 {
			t.Errorf("max_read_bytes = %d, want 1", ws.MaxReadBytes)
		}
		if ws.MaxWriteBytes != 1073741824 {
			t.Errorf("max_write_bytes = %d, want 1073741824", ws.MaxWriteBytes)
		}
		if ws.MaxOutputBytes != 1 {
			t.Errorf("max_output_bytes = %d, want 1", ws.MaxOutputBytes)
		}
		if ws.MaxCommandTimeoutMS != 1 {
			t.Errorf("max_command_timeout_ms = %d, want 1", ws.MaxCommandTimeoutMS)
		}
	})

	t.Run("boundary limit values at max load", func(t *testing.T) {
		yaml := fmt.Sprintf(baseNode+`
  - id: "node-ws-boundary-max"
    alias: "boundary-max-node"
    token: "token-boundary-max"
    providers:
      - id: "prov-a"
        type: "ollama"
        category: "local_inference"
        models: ["model-a"]
        capacity: 2
    workspaces:
      - ref: "ws-boundary-max"
        platform: "darwin"
        root: "/Users/operator/projects/test"
        operations:
          - "read"
          - "write"
          - "delete"
          - "command"
        commands:
          - id: "cmd-boundary-max"
            executable: "/usr/bin/cat"
            args: []
        max_read_bytes: %d
        max_write_bytes: %d
        max_output_bytes: %d
        max_command_timeout_ms: 3600000
`, 1073741824, 1073741824, 1073741824)
		if err := os.WriteFile(f, []byte(yaml), 0o600); err != nil {
			t.Fatalf("write yaml: %v", err)
		}
		cfg, err := config.LoadEdge(f)
		if err != nil {
			t.Fatalf("load: %v", err)
		}
		ws := cfg.Nodes[0].Workspaces[0]
		if ws.MaxReadBytes != 1073741824 {
			t.Errorf("max_read_bytes = %d, want 1073741824", ws.MaxReadBytes)
		}
		if ws.MaxWriteBytes != 1073741824 {
			t.Errorf("max_write_bytes = %d, want 1073741824", ws.MaxWriteBytes)
		}
		if ws.MaxOutputBytes != 1073741824 {
			t.Errorf("max_output_bytes = %d, want 1073741824", ws.MaxOutputBytes)
		}
		if ws.MaxCommandTimeoutMS != 3600000 {
			t.Errorf("max_command_timeout_ms = %d, want 3600000", ws.MaxCommandTimeoutMS)
		}
	})
}
