#!/bin/bash
set -euo pipefail

# test-resolve-seat.sh - test resolve-seat.sh implementation
# Follows run-plan-lib.sh test pattern

SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
REAL_SCRIPT="$SCRIPT_DIR/resolve-seat.sh"
if [[ ! -x "$REAL_SCRIPT" ]]; then
  echo "ERROR: resolve-seat.sh not found or not executable" >&2
  exit 1
fi

WORK_DIR=$(mktemp -d)
trap 'rm -rf "$WORK_DIR"' EXIT
mkdir -p "$WORK_DIR/lib"
cp "$REAL_SCRIPT" "$WORK_DIR/lib/resolve-seat.sh"
chmod +x "$WORK_DIR/lib/resolve-seat.sh"
test_file="$WORK_DIR/lib/resolve-seat.sh"
mkdir -p "$WORK_DIR/presets"

FAILURES=0
record_failure() { FAILURES=$((FAILURES + 1)); }

# Create test preset 1: anthropic (from spec/PRESETS.md)
cat > "$WORK_DIR/presets/anthropic.json" << 'EOF'
{
  "name": "anthropic",
  "version": "preset/v1",
  "seats": {
    "coder":    { "wrapper": "skills/cursor-orchestrator/ca.sh", "model": "composer-2.5" },
    "reviewer": { "wrapper": "workflows/lib/opus-review.sh", "model": "claude-opus-4-8" }
  }
}
EOF

# Create test preset 2: anthropic-less (tiered, from spec/PRESETS.md)
cat > "$WORK_DIR/presets/anthropic-less.json" << 'EOF'
{
  "name": "anthropic-less",
  "version": "preset/v1",
  "seats": {
    "coder": {
      "low":    { "wrapper": "skills/north-orchestrator/na.sh", "model": "cohere/north-mini-code:free" },
      "medium": { "wrapper": "skills/cursor-orchestrator/ca.sh", "model": "composer-2.5" },
      "high":   { "wrapper": "workflows/harness/wrappers/codex.sh", "model": "gpt-5.5-high", "fallback": "coder.medium" }
    },
    "reviewer": { "wrapper": "workflows/harness/wrappers/codex.sh", "model": "gpt-5.5-low" },
    "fixer":    { "wrapper": "skills/cursor-orchestrator/ca.sh", "model": "composer-2.5" }
  }
}
EOF

# Helper function to create plan lines
create_plan_line() {
  local id="$1"
  local seat="$2"
  local tier="$3"
  local task="$4"

  if [[ -n "$tier" ]]; then
    echo "{\"id\": \"$id\", \"seat\": \"$seat\", \"tier\": \"$tier\", \"task\": \"$task\"}"
  else
    echo "{\"id\": \"$id\", \"seat\": \"$seat\", \"task\": \"$task\"}"
  fi
}

# Helper function to create runconfig
create_runconfig() {
  local preset="$1"
  local overrides="$2"

  local runconfig='{"version": "runconfig/v1", "preset": "'$preset'"}'

  if [[ -n "$overrides" ]]; then
    runconfig=$(echo "$runconfig" | jq --argjson overrides "$overrides" '. + {overrides: $overrides}')
  fi

  echo "$runconfig"
}

# Test helper function
run_test() {
  local test_name="$1"
  local node_id="$2"
  local seat="$3"
  local tier="$4"
  local preset="$5"
  local overrides_json="${6:-}"
  local expected_model="${7:-}"

  echo "=== Test: $test_name ==="

  # Create plan file with single node
  local plan_line=$(create_plan_line "$node_id" "$seat" "$tier" "test-task")
  cat > "$WORK_DIR/test-plan-${node_id}.jsonl" << EOF
$plan_line
EOF

  # Create runconfig file
  local runconfig=$(create_runconfig "$preset" "$overrides_json")
  echo "$runconfig" > "$WORK_DIR/test-runconfig-${node_id}.json"

  # Run resolve-seat.sh
  local output
  if output=$(cd "$WORK_DIR" && "$test_file" --plan "$WORK_DIR/test-plan-${node_id}.jsonl" --runconfig "$WORK_DIR/test-runconfig-${node_id}.json" --node "$node_id" 2>/dev/null); then
    echo "  Output: $output"
    local test_result=0
    if [[ -n "$expected_model" ]]; then
      if ! echo "$output" | jq -e --arg m "$expected_model" '.model == $m' > /dev/null 2>&1; then
        echo "✗ FAIL: $test_name - expected model '$expected_model', got '$(echo "$output" | jq -r '.model')'"
        test_result=1
      fi
    fi
    if [[ $test_result -eq 0 ]]; then
      echo "✓ PASS: $test_name"
    fi
  else
    echo "✗ FAIL: $test_name"
    echo "  Exit code: $?"
    local test_result=1
  fi

  # Cleanup
  rm -f "$WORK_DIR/test-plan-${node_id}.jsonl" "$WORK_DIR/test-runconfig-${node_id}.json"

  return $test_result
}

# Test helper function for negative tests (should fail)
run_negative_test() {
  local test_name="$1"
  local node_id="$2"
  local seat="$3"
  local tier="$4"
  local preset="$5"
  shift 5

  local overrides_json="$@"

  echo "=== Test: $test_name ==="

  # Create plan file with single node
  local plan_line=$(create_plan_line "$node_id" "$seat" "$tier" "test-task")
  cat > "$WORK_DIR/test-plan-${node_id}.jsonl" << EOF
$plan_line
EOF

  # Create runconfig file
  local runconfig=$(create_runconfig "$preset" "$overrides_json")
  echo "$runconfig" > "$WORK_DIR/test-runconfig-${node_id}.json"

  # Run resolve-seat.sh - should fail
  if ! (cd "$WORK_DIR" && "$test_file" --plan "$WORK_DIR/test-plan-${node_id}.jsonl" --runconfig "$WORK_DIR/test-runconfig-${node_id}.json" --node "$node_id" 2>&1); then
    echo "✓ PASS: $test_name - correctly rejected"
    local test_result=0
  else
    echo "✗ FAIL: $test_name - should have failed"
    local test_result=1
  fi

  # Cleanup
  rm -f "$WORK_DIR/test-plan-${node_id}.jsonl" "$WORK_DIR/test-runconfig-${node_id}.json"

  return $test_result
}

# Test 1: Basic resolution - seat only (from spec/PRESETS.md)
run_test "Basic coder seat (tiered preset)" coder1 coder medium anthropic-less || record_failure

# Test 2: Tier-specific resolution
run_test "Coder seat with tier low" coder2 coder low anthropic-less || record_failure

# Test 3: Tier-specific resolution with high tier
run_test "Coder seat with tier high" coder3 coder high anthropic-less || record_failure

# Test 4: Runconfig override (seat.tier binding)
run_test "Runconfig override coder.medium" coder4 coder medium anthropic-less '{"coder.medium": {"wrapper": "skills/cursor-orchestrator/ca.sh", "model": "custom-model"}}' || record_failure

# Test 5: Bare seat override (no tier in key)
run_test "Bare seat override coder" coder5 coder high anthropic-less '{"coder": {"wrapper": "skills/north-orchestrator/na.sh", "model": "bare-override-model"}}' "bare-override-model" || record_failure

# Test 6: Review seat (flat binding)
run_test "Review seat (flat binding)" reviewer1 reviewer medium anthropic-less || record_failure

# Test 7: Fixer seat (flat binding)
run_test "Fixer seat (flat binding)" fixer1 fixer medium anthropic-less || record_failure

# Test 8: Deterministic gate rejection (gate0) - fail closed
run_negative_test "Gate0 rejection" gate0 gate0 medium anthropic || record_failure

# Test 9: Deterministic gate rejection (risk) - fail closed
run_negative_test "Risk rejection" risk-node risk medium anthropic || record_failure

# Test 10: Unknown seat fail-closed
run_negative_test "Unknown seat rejection" unknown-node nonexistent-seat medium anthropic || record_failure

# Test 11: Unknown tier (with existing seat) - fail closed
run_negative_test "Unknown tier rejection" unknown-tier-node coder nonexistent-tier anthropic-less || record_failure

# Test 12: Override present for seat/tier that doesn't match request; seat absent from preset → BLOCK
run_negative_test "Runconfig override unknown seat" override-unknown nonexistent-seat medium anthropic '{"nonexistent-seat.high": {"wrapper": "custom.sh", "model": "test"}}' || record_failure

# Test 13: Tiered seat with override takes precedence
run_test "Override takes precedence over preset" override-precedence coder medium anthropic-less '{"coder.medium": {"wrapper": "skills/north-orchestrator/na.sh", "model": "overridden-model"}}' || record_failure

# Test 14: seat.tier override takes precedence over bare seat override
run_test "seat.tier override beats bare seat override" override-tier-beats-bare coder medium anthropic-less \
  '{"coder": {"wrapper": "skills/north-orchestrator/na.sh", "model": "bare-model"}, "coder.medium": {"wrapper": "skills/cursor-orchestrator/ca.sh", "model": "tier-model"}}' \
  "tier-model" || record_failure

# Test 15: Wrapper-only binding rejected (no model)
run_negative_test "Wrapper-only binding rejected (no model)" wrapper-only coder medium anthropic-less '{"coder.medium": {"wrapper": "test.sh"}}' || record_failure

# Test 16: Bare seat override missing model → rejected
run_negative_test "Bare seat override missing model" bare-no-model coder medium anthropic-less '{"coder": {"wrapper": "test.sh"}}' || record_failure

# Test: Malformed unselected override (missing model) is rejected upfront
run_negative_test "Unselected override missing model rejected upfront" unselected-bad-binding coder medium anthropic-less '{"coder.high": {"wrapper": "test.sh"}}' || record_failure

# Test: Fallback object with wrapper but missing model → rejected (exit 2)
echo "=== Test: Fallback object wrapper-only rejected (exit 2) ==="
_rc=$(echo '{"version":"runconfig/v1","preset":"anthropic-less"}' | jq --argjson ov '{"coder.medium":{"wrapper":"test.sh","model":"m","fallback":{"wrapper":"fallback.sh"}}}' '. + {overrides:$ov}')
echo "$_rc" > "$WORK_DIR/fallback-wrapper-only-runconfig.json"
echo '{"id":"fb-wo","seat":"coder","tier":"medium","task":"t"}' > "$WORK_DIR/fallback-wrapper-only-plan.jsonl"
_exit=0
_stderr=$(cd "$WORK_DIR" && bash "$test_file" --plan "$WORK_DIR/fallback-wrapper-only-plan.jsonl" --runconfig "$WORK_DIR/fallback-wrapper-only-runconfig.json" --node "fb-wo" 2>&1 >/dev/null) || _exit=$?
if [[ $_exit -eq 2 ]] && echo "$_stderr" | jq -e '.ok == false' > /dev/null 2>&1; then
  echo "✓ PASS: Fallback object wrapper-only rejected (exit 2)"
else
  echo "✗ FAIL: Fallback object wrapper-only should exit 2 with ok:false JSON (exit=$_exit)"
  record_failure
fi
rm -f "$WORK_DIR/fallback-wrapper-only-runconfig.json" "$WORK_DIR/fallback-wrapper-only-plan.jsonl"

# Test: Fallback array with wrapper-only object → rejected (exit 2)
echo "=== Test: Fallback array wrapper-only object rejected (exit 2) ==="
_rc=$(echo '{"version":"runconfig/v1","preset":"anthropic-less"}' | jq --argjson ov '{"coder.medium":{"wrapper":"test.sh","model":"m","fallback":[{"wrapper":"fallback.sh"}]}}' '. + {overrides:$ov}')
echo "$_rc" > "$WORK_DIR/fallback-array-wrapper-only-runconfig.json"
echo '{"id":"fb-arr","seat":"coder","tier":"medium","task":"t"}' > "$WORK_DIR/fallback-array-wrapper-only-plan.jsonl"
_exit=0
_stderr=$(cd "$WORK_DIR" && bash "$test_file" --plan "$WORK_DIR/fallback-array-wrapper-only-plan.jsonl" --runconfig "$WORK_DIR/fallback-array-wrapper-only-runconfig.json" --node "fb-arr" 2>&1 >/dev/null) || _exit=$?
if [[ $_exit -eq 2 ]] && echo "$_stderr" | jq -e '.ok == false' > /dev/null 2>&1; then
  echo "✓ PASS: Fallback array wrapper-only object rejected (exit 2)"
else
  echo "✗ FAIL: Fallback array wrapper-only object should exit 2 with ok:false JSON (exit=$_exit)"
  record_failure
fi
rm -f "$WORK_DIR/fallback-array-wrapper-only-runconfig.json" "$WORK_DIR/fallback-array-wrapper-only-plan.jsonl"

# Test 17: Extra top-level runconfig field → rejected
echo "=== Test: Extra top-level runconfig field rejection ==="
plan_line=$(create_plan_line "extra-root-field" coder medium "test-task")
cat > "$WORK_DIR/test-plan-extra-root-field.jsonl" << EOF
$plan_line
EOF
cat > "$WORK_DIR/test-runconfig-extra-root-field.json" << 'EOF'
{"version": "runconfig/v1", "preset": "anthropic-less", "unknown_field": "x"}
EOF
if ! (cd "$WORK_DIR" && "$test_file" --plan "$WORK_DIR/test-plan-extra-root-field.jsonl" --runconfig "$WORK_DIR/test-runconfig-extra-root-field.json" --node "extra-root-field" 2>&1); then
  echo "✓ PASS: Extra top-level runconfig field rejection - correctly rejected"
else
  echo "✗ FAIL: Extra top-level runconfig field rejection - should have failed"
  record_failure
fi
rm -f "$WORK_DIR/test-plan-extra-root-field.jsonl" "$WORK_DIR/test-runconfig-extra-root-field.json"

# Test 18: Invalid override key → rejected
run_negative_test "Invalid override key rejection" invalid-override-key coder medium anthropic-less '{"INVALID_KEY": {"wrapper": "test.sh", "model": "m"}}' || record_failure

# Test 19: Override value with unknown binding field → rejected
run_negative_test "Unknown binding field rejection" unknown-binding-field coder medium anthropic-less '{"coder.medium": {"wrapper": "test.sh", "model": "m", "unknown_field": "x"}}' || record_failure

echo "=== Test: Wrapper-only binding emits exit-2 JSON on stderr ==="
_rc=$(echo '{"version":"runconfig/v1","preset":"anthropic-less"}' | jq --argjson ov '{"coder.medium":{"wrapper":"test.sh"}}' '. + {overrides:$ov}')
echo "$_rc" > "$WORK_DIR/wrapper-only-runconfig.json"
echo '{"id":"wo","seat":"coder","tier":"medium","task":"t"}' > "$WORK_DIR/wrapper-only-plan.jsonl"
_stderr=$(cd "$WORK_DIR" && bash "$test_file" --plan "$WORK_DIR/wrapper-only-plan.jsonl" --runconfig "$WORK_DIR/wrapper-only-runconfig.json" --node "wo" 2>&1 >/dev/null || true)
if echo "$_stderr" | jq -e '.ok == false' > /dev/null 2>&1; then
  echo "✓ PASS: Wrapper-only binding emits exit-2 JSON"
else
  echo "✗ FAIL: Wrapper-only binding did not emit ok:false JSON on stderr"
  record_failure
fi
rm -f "$WORK_DIR/wrapper-only-runconfig.json" "$WORK_DIR/wrapper-only-plan.jsonl"

# Test 16: Missing --plan value
echo "=== Test: Missing --plan value ==="
if ! (cd "$WORK_DIR" && bash "$test_file" --plan 2>/dev/null); then
  stderr_out=$(cd "$WORK_DIR" && bash "$test_file" --plan 2>&1 >/dev/null || true)
  if echo "$stderr_out" | jq -e '.ok == false' > /dev/null 2>&1; then
    echo "✓ PASS: Missing --plan value emits exit-2 JSON"
  else
    echo "✗ FAIL: Missing --plan value did not emit valid JSON on stderr"
    record_failure
  fi
else
  echo "✗ FAIL: Missing --plan value should have exited non-zero"
  record_failure
fi

# Test 17: Missing --runconfig value
echo "=== Test: Missing --runconfig value ==="
if ! (cd "$WORK_DIR" && bash "$test_file" --runconfig 2>/dev/null); then
  stderr_out=$(cd "$WORK_DIR" && bash "$test_file" --runconfig 2>&1 >/dev/null || true)
  if echo "$stderr_out" | jq -e '.ok == false' > /dev/null 2>&1; then
    echo "✓ PASS: Missing --runconfig value emits exit-2 JSON"
  else
    echo "✗ FAIL: Missing --runconfig value did not emit valid JSON on stderr"
    record_failure
  fi
else
  echo "✗ FAIL: Missing --runconfig value should have exited non-zero"
  record_failure
fi

# Test 18: Missing --node value
echo "=== Test: Missing --node value ==="
if ! (cd "$WORK_DIR" && bash "$test_file" --node 2>/dev/null); then
  stderr_out=$(cd "$WORK_DIR" && bash "$test_file" --node 2>&1 >/dev/null || true)
  if echo "$stderr_out" | jq -e '.ok == false' > /dev/null 2>&1; then
    echo "✓ PASS: Missing --node value emits exit-2 JSON"
  else
    echo "✗ FAIL: Missing --node value did not emit valid JSON on stderr"
    record_failure
  fi
else
  echo "✗ FAIL: Missing --node value should have exited non-zero"
  record_failure
fi

# Verify output structure
verify_output_structure() {
  local test_name="$1"
  local seat="$2"
  local tier="$3"
  local preset="$4"
  local failed=0

  echo "=== Test: Verify output structure ($test_name) ==="

  # Create plan file
  local plan_line=$(create_plan_line "verify-node" "$seat" "$tier" "test-task")
  cat > "$WORK_DIR/verify-plan.jsonl" << EOF
$plan_line
EOF

  # Create runconfig file
  local runconfig=$(create_runconfig "$preset" "")
  echo "$runconfig" > "$WORK_DIR/verify-runconfig.json"

  # Run resolve-seat.sh
  local output
  if output=$(cd "$WORK_DIR" && "$test_file" --plan "$WORK_DIR/verify-plan.jsonl" --runconfig "$WORK_DIR/verify-runconfig.json" --node "verify-node" 2>/dev/null); then
    # Verify output is valid JSON
    if echo "$output" | jq -e '.' > /dev/null 2>&1; then
      echo "  Output is valid JSON"

      # Verify required fields exist
      if echo "$output" | jq -e 'has("seat") and has("tier") and has("wrapper") and has("model")' > /dev/null 2>&1; then
        echo "  Output has required fields: seat, tier, wrapper, model"

        # Verify seat and tier match expected values
        local actual_seat=$(echo "$output" | jq -r '.seat')
        local actual_tier=$(echo "$output" | jq -r '.tier')

        if [[ "$actual_seat" == "$seat" && "$actual_tier" == "$tier" ]]; then
          echo "  Seat and tier match expected values"
        else
          echo "  ERROR: Seat/tier mismatch. Expected $seat/$tier, got $actual_seat/$actual_tier"
          failed=1
        fi
      else
        echo "  ERROR: Missing required fields in output"
        failed=1
      fi
    else
      echo "  ERROR: Output is not valid JSON"
      failed=1
    fi
  else
    echo "✗ FAIL: $test_name - resolve-seat.sh failed unexpectedly"
    echo "  Exit code: $?"
    failed=1
  fi

  if [[ $failed -eq 0 ]]; then
    echo "✓ PASS: Output structure for $seat/$tier"
  else
    echo "✗ FAIL: Output structure for $seat/$tier"
  fi

  # Cleanup
  rm -f "$WORK_DIR/verify-plan.jsonl" "$WORK_DIR/verify-runconfig.json"
  return $failed
}

# Run verification tests
verify_output_structure "Coder seat output" coder medium anthropic-less || record_failure
verify_output_structure "Review seat output" reviewer medium anthropic-less || record_failure

# Run integration test with all seats
test_integration() {
  echo "=== Test: Integration with all seats ==="

  # Create plan with multiple nodes
  cat > "$WORK_DIR/integration-plan.jsonl" << EOF
{"id": "coder-low", "seat": "coder", "tier": "low", "task": "test"}
{"id": "coder-medium", "seat": "coder", "tier": "medium", "task": "test"}
{"id": "reviewer", "seat": "reviewer", "tier": "medium", "task": "test"}
EOF

  # Create runconfig using anthropic-less preset
  local runconfig=$(create_runconfig "anthropic-less" "")
  echo "$runconfig" > "$WORK_DIR/integration-runconfig.json"

  # Test each node
  local all_passed=true

  # Test coder/low
  if output=$(cd "$WORK_DIR" && "$test_file" --plan "$WORK_DIR/integration-plan.jsonl" --runconfig "$WORK_DIR/integration-runconfig.json" --node "coder-low" 2>/dev/null); then
    echo "✓ PASS: coder/low resolution"
    if echo "$output" | jq -e '.seat == "coder" and .tier == "low"' > /dev/null 2>&1; then
      echo "  Seat and tier correct"
    else
      echo "  ERROR: Seat/tier incorrect"
      all_passed=false
    fi
  else
    echo "✗ FAIL: coder/low resolution"
    all_passed=false
  fi

  # Test coder/medium
  if output=$(cd "$WORK_DIR" && "$test_file" --plan "$WORK_DIR/integration-plan.jsonl" --runconfig "$WORK_DIR/integration-runconfig.json" --node "coder-medium" 2>/dev/null); then
    echo "✓ PASS: coder/medium resolution"
    if echo "$output" | jq -e '.seat == "coder" and .tier == "medium"' > /dev/null 2>&1; then
      echo "  Seat and tier correct"
    else
      echo "  ERROR: Seat/tier incorrect"
      all_passed=false
    fi
  else
    echo "✗ FAIL: coder/medium resolution"
    all_passed=false
  fi

  # Test reviewer
  if output=$(cd "$WORK_DIR" && "$test_file" --plan "$WORK_DIR/integration-plan.jsonl" --runconfig "$WORK_DIR/integration-runconfig.json" --node "reviewer" 2>/dev/null); then
    echo "✓ PASS: reviewer resolution"
    if echo "$output" | jq -e '.seat == "reviewer" and .tier == "medium"' > /dev/null 2>&1; then
      echo "  Seat and tier correct"
    else
      echo "  ERROR: Seat/tier incorrect"
      all_passed=false
    fi
  else
    echo "✗ FAIL: reviewer resolution"
    all_passed=false
  fi

  if [[ "$all_passed" == "true" ]]; then
    echo "✓ PASS: Integration test"
  else
    echo "✗ FAIL: Integration test - some resolutions incorrect"
  fi

  # Cleanup
  rm -f "$WORK_DIR/integration-plan.jsonl" "$WORK_DIR/integration-runconfig.json"

  if [[ "$all_passed" != "true" ]]; then
    return 1
  fi
}

test_integration || record_failure

if [[ $FAILURES -gt 0 ]]; then
  echo "=== $FAILURES test(s) failed ==="
  exit 1
fi

echo "=== All tests completed ==="
chmod +x "$test_file"
