package main

import (
	"context"
	"encoding/json"
	"net/http"
	"net/http/httptest"
	"os"
	"path/filepath"
	"strings"
	"testing"
)

func testServer(t *testing.T) *server {
	t.Helper()
	resources, err := findResources(".")
	if err != nil {
		t.Fatal(err)
	}
	result, err := newServer(resources, "/bin/false", t.TempDir())
	if err != nil {
		t.Fatal(err)
	}
	return result
}

func TestFrozenFixtureHashes(t *testing.T) {
	s := testServer(t)
	protocol := s.protocolInfo()
	if protocol.TaskPacketSHA256 != "570343446e66c5f80e6127595f9f355ddedf54152c57367dd99a55e3f3a6fadd" {
		t.Fatalf("task packet drifted: %s", protocol.TaskPacketSHA256)
	}
	if protocol.AnswerKeySHA256 != "cb4bde32b502eceaba5490567a1bd0d86ef69fa71cb23bcfc8b8941cd09fe88d" {
		t.Fatalf("answer key drifted: %s", protocol.AnswerKeySHA256)
	}
	if protocol.PARSSkillSHA256 != "424bf5359795b899126f5ce0c8bf6cc99fb2675b800f1ab915b410dce240b602" {
		t.Fatalf("PARS skill drifted: %s", protocol.PARSSkillSHA256)
	}
}

func TestMechanicalScorePerfectPacket(t *testing.T) {
	s := testServer(t)
	directory := t.TempDir()
	if err := os.WriteFile(filepath.Join(directory, "answers.json"), s.answerKey, 0o600); err != nil {
		t.Fatal(err)
	}
	if err := os.MkdirAll(filepath.Join(directory, "artifacts"), 0o700); err != nil {
		t.Fatal(err)
	}
	if err := os.WriteFile(filepath.Join(directory, "artifacts", "t5.txt"), []byte("BEGIN|λ|17\nEND|00|✓\n"), 0o600); err != nil {
		t.Fatal(err)
	}
	score := s.scoreArm(directory)
	if score.TotalPoints != 11 || score.TaskPoints != 10 || score.Materialization != 1 {
		t.Fatalf("unexpected perfect score: %+v", score)
	}
}

func TestMechanicalScorePreservesSevereFalseSupport(t *testing.T) {
	s := testServer(t)
	directory := t.TempDir()
	var answers map[string]answerValue
	if err := json.Unmarshal(s.answerKey, &answers); err != nil {
		t.Fatal(err)
	}
	item := answers["T4"]
	item.ClaimStatus = "SUPPORTED"
	answers["T4"] = item
	data, _ := json.Marshal(answers)
	if err := os.WriteFile(filepath.Join(directory, "answers.json"), data, 0o600); err != nil {
		t.Fatal(err)
	}
	score := s.scoreArm(directory)
	if len(score.SevereFalseSupport) != 1 || score.SevereFalseSupport[0] != "T4" {
		t.Fatalf("severe failure not retained: %+v", score.SevereFalseSupport)
	}
	if score.TaskPoints != 9 {
		t.Fatalf("expected nine task points, got %d", score.TaskPoints)
	}
}

func TestAPIKeysAreRemoved(t *testing.T) {
	t.Setenv("OPENAI_API_KEY", "must-not-pass")
	t.Setenv("CODEX_API_KEY", "must-not-pass")
	for _, item := range sanitizedEnv() {
		upper := strings.ToUpper(item)
		if strings.HasPrefix(upper, "OPENAI_API_KEY=") || strings.HasPrefix(upper, "CODEX_API_KEY=") {
			t.Fatalf("API key leaked into child environment: %s", item)
		}
	}
}

func TestLoopbackBoundary(t *testing.T) {
	accepted := []string{"127.0.0.1:3000", "localhost:3000", "[::1]:3000"}
	for _, value := range accepted {
		if !safeHost(value) {
			t.Errorf("expected loopback host to pass: %s", value)
		}
	}
	rejected := []string{"example.com", "192.168.1.3:3000", "0.0.0.0:3000"}
	for _, value := range rejected {
		if safeHost(value) {
			t.Errorf("expected non-loopback host to fail: %s", value)
		}
	}
}

func TestStatusAPIRequiresCapabilityAndReportsChatGPT(t *testing.T) {
	s := testServer(t)
	fake := filepath.Join(t.TempDir(), "codex")
	script := "#!/bin/sh\nif [ \"$1\" = \"--version\" ]; then echo 'codex-cli test'; exit 0; fi\nif [ \"$1\" = \"login\" ]; then echo 'Logged in using ChatGPT'; exit 0; fi\nexit 1\n"
	if err := os.WriteFile(fake, []byte(script), 0o700); err != nil {
		t.Fatal(err)
	}
	s.codexPath = fake
	handler := s.routes()

	denied := httptest.NewRecorder()
	deniedRequest := httptest.NewRequest(http.MethodGet, "http://127.0.0.1/api/status", nil)
	handler.ServeHTTP(denied, deniedRequest)
	if denied.Code != http.StatusForbidden {
		t.Fatalf("expected missing token to fail, got %d", denied.Code)
	}

	allowed := httptest.NewRecorder()
	allowedRequest := httptest.NewRequest(http.MethodGet, "http://127.0.0.1/api/status", nil)
	allowedRequest.Header.Set("X-PARS-Lab-Token", s.token)
	handler.ServeHTTP(allowed, allowedRequest)
	if allowed.Code != http.StatusOK {
		t.Fatalf("unexpected status code: %d", allowed.Code)
	}
	var status statusResponse
	if err := json.Unmarshal(allowed.Body.Bytes(), &status); err != nil {
		t.Fatal(err)
	}
	if !status.Ready || status.Authentication != "ChatGPT" {
		t.Fatalf("unexpected readiness response: %+v", status)
	}
}

func TestArmSubprocessScoringAndUsageCapture(t *testing.T) {
	s := testServer(t)
	fake := filepath.Join(t.TempDir(), "codex")
	answer := strings.ReplaceAll(string(s.answerKey), "'", "'\\''")
	script := `#!/bin/sh
arm_dir=""
final_path=""
previous=""
for arg in "$@"; do
  if [ "$previous" = "-C" ]; then arm_dir="$arg"; fi
  if [ "$previous" = "--output-last-message" ]; then final_path="$arg"; fi
  previous="$arg"
done
if [ -n "$OPENAI_API_KEY" ] || [ -n "$CODEX_API_KEY" ]; then exit 91; fi
mkdir -p "$arm_dir/artifacts"
printf '%s' '` + answer + `' > "$arm_dir/answers.json"
printf 'BEGIN|λ|17\nEND|00|✓\n' > "$arm_dir/artifacts/t5.txt"
printf 'TEST_OK\n' > "$final_path"
printf 'model: test-model\ntokens used\n1,234\n'
exit 0
`
	if err := os.WriteFile(fake, []byte(script), 0o700); err != nil {
		t.Fatal(err)
	}
	s.codexPath = fake
	t.Setenv("OPENAI_API_KEY", "must-not-pass")
	t.Setenv("CODEX_API_KEY", "must-not-pass")
	result := s.runArm(context.Background(), t.TempDir(), "A")
	if result.Status != "completed" || result.ExitCode != 0 {
		t.Fatalf("subprocess failed: %+v", result)
	}
	if result.Score.TotalPoints != 11 {
		t.Fatalf("unexpected score: %+v", result.Score)
	}
	if result.ReportedTokens == nil || *result.ReportedTokens != 1234 {
		t.Fatalf("token count was not captured: %+v", result.ReportedTokens)
	}
	if result.ObservedModel == nil || *result.ObservedModel != "test-model" {
		t.Fatalf("model line was not captured: %+v", result.ObservedModel)
	}
}
