package cmd

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

// refreshEngine: the catalog image is present and pull reports a newer one.
const refreshEngine = `#!/bin/sh
echo "$@" >> "$FAKE_LOG"
case "$1" in
  info) exit 0 ;;
  image) [ "$3" = "docker.io/parrotsec/security" ] && exit 0; exit 1 ;;
  pull) echo "Status: Downloaded newer image for $2"; exit 0 ;;
esac
exit 0
`

func catalogServer(t *testing.T, body string) *httptest.Server {
	t.Helper()
	srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		w.Write([]byte(body))
	}))
	t.Cleanup(srv.Close)
	return srv
}

func TestRefreshAlreadyUpToDate(t *testing.T) {
	log := fakeRuntime(t, "podman", refreshEngine)
	srv := catalogServer(t, testCatalogYAML) // same version as the system copy
	app, stdout, _ := testApp(t, map[string]string{"NEST_CATALOG_URL": srv.URL})

	if code := app.Run([]string{"refresh"}); code != exitOK {
		t.Fatalf("exit = %d", code)
	}
	out := stdout.String()
	if !strings.Contains(out, "catalog already up to date") {
		t.Errorf("output:\n%s", out)
	}
	if _, err := os.Stat(app.CatalogLocs.User); !os.IsNotExist(err) {
		t.Error("user catalog must not be written when the download is not newer")
	}

	// image section still runs: the shared image is local and got newer layers
	if !strings.Contains(out, "image docker.io/parrotsec/security: updated") {
		t.Errorf("image status missing or wrong:\n%s", out)
	}
	if strings.Count(readLog(t, log), "pull docker.io/parrotsec/security") != 1 {
		t.Errorf("shared image must be pulled exactly once per run:\n%s", readLog(t, log))
	}
}

func TestRefreshRejectsUnsupportedVersion(t *testing.T) {
	// §2.3: only version 1 is supported, so a v2 download is invalid and
	// changes nothing — the update path opens when the schema evolves.
	srv := catalogServer(t, strings.Replace(testCatalogYAML, "version: 1", "version: 2", 1))
	app, _, _ := testApp(t, map[string]string{"NEST_CATALOG_URL": srv.URL})

	if code := app.Run([]string{"refresh"}); code != exitError {
		t.Errorf("exit = %d, want 1", code)
	}
	if _, err := os.Stat(app.CatalogLocs.User); !os.IsNotExist(err) {
		t.Error("an invalid download must change nothing")
	}
}

func TestRefreshInvalidDownloadKeepsCurrent(t *testing.T) {
	srv := catalogServer(t, "version: [")
	app, _, _ := testApp(t, map[string]string{"NEST_CATALOG_URL": srv.URL})

	if code := app.Run([]string{"refresh"}); code != exitError {
		t.Errorf("exit = %d, want 1", code)
	}
	if _, err := os.Stat(app.CatalogLocs.User); !os.IsNotExist(err) {
		t.Error("an invalid download must change nothing")
	}
}

func TestRefreshUnreachableURL(t *testing.T) {
	app, _, _ := testApp(t, map[string]string{"NEST_CATALOG_URL": "http://127.0.0.1:1/unreachable"})
	if code := app.Run([]string{"refresh"}); code != exitError {
		t.Errorf("exit = %d, want 1", code)
	}
}

func TestRefreshJSONWithoutRuntime(t *testing.T) {
	srv := catalogServer(t, testCatalogYAML)
	t.Setenv("PATH", t.TempDir()) // no runtime available
	app, stdout, _ := testApp(t, map[string]string{"NEST_CATALOG_URL": srv.URL})

	if code := app.Run([]string{"refresh", "--json"}); code != exitOK {
		t.Fatalf("exit = %d, want 0 (catalog part still succeeds)", code)
	}
	if !strings.Contains(stdout.String(), `"images": [`) {
		t.Errorf("images key must be present and empty without a runtime:\n%s", stdout.String())
	}
}

func TestWriteAtomic(t *testing.T) {
	path := filepath.Join(t.TempDir(), "sub", "tools.yaml")
	if err := writeAtomic(path, []byte("version: 1\n")); err != nil {
		t.Fatalf("writeAtomic: %v", err)
	}
	data, err := os.ReadFile(path)
	if err != nil || string(data) != "version: 1\n" {
		t.Errorf("got %q, %v", data, err)
	}
	// no temp files left behind
	entries, err := os.ReadDir(filepath.Dir(path))
	if err != nil {
		t.Fatal(err)
	}
	if len(entries) != 1 {
		t.Errorf("%d files left in dir, want exactly 1", len(entries))
	}
}
