package service

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

func TestRefreshDevicesAndReplacementWorkflow(t *testing.T) {
	kube := fakeKubeServer(t)
	harbor := fakeHarborServer(t, true)
	app := newTestApp(t)
	app.settings.Namespace = "maintenance"
	app.settings.RunnerImageARM64 = "runner:arm64"
	app.settings.HarborAPIBase = harbor.URL + "/api/v2.0"
	app.settings.HarborUsername = "admin"
	app.settings.HarborPassword = "pw"
	app.settings.HarborProject = "metis"
	app.settings.HarborRegistry = "registry.example"
	app.settings.ArtifactStatePath = filepath.Join(t.TempDir(), "artifacts.json")

	installKubeFactory(t, kube)

	devices, err := app.RefreshDevices("titan-22")
	if err != nil {
		t.Fatalf("RefreshDevices: %v", err)
	}
	if len(devices) < 2 || devices[0].Path != "/dev/sdz" {
		t.Fatalf("unexpected devices: %+v", devices)
	}
	cached, err := app.ListDevices("titan-22")
	if err != nil || len(cached) != len(devices) {
		t.Fatalf("ListDevices cache mismatch: %+v err=%v", cached, err)
	}
	state := app.State("titan-22")
	if state.PreferredDevice != "/dev/sdz" {
		t.Fatalf("expected preferred device /dev/sdz, got %q", state.PreferredDevice)
	}

	job, err := app.Replace("titan-15", "titan-22", "/dev/sdz")
	if err != nil {
		t.Fatalf("Replace: %v", err)
	}
	waitForJobState(t, app, job.ID, JobDone)
	if got := app.job(job.ID); got == nil || got.Status != JobDone {
		t.Fatalf("replace job did not finish successfully: %#v", got)
	}
	if got := app.artifacts()["titan-15"].Ref; got != "registry.example/metis/titan-15:latest" {
		t.Fatalf("artifact not recorded: %q", got)
	}
}

func TestRemotePodStateAndLogsHelpers(t *testing.T) {
	kube := fakeKubeServer(t)
	installKubeFactory(t, kube)
	app := newTestApp(t)
	app.settings.Namespace = "maintenance"
	client, err := kubeClientFactory()
	if err != nil {
		t.Fatalf("kube client: %v", err)
	}
	state, err := app.remotePodState(client, "metis-build-test")
	if err != nil {
		t.Fatalf("remotePodState: %v", err)
	}
	if state.Name != "metis-build-test" || state.Reason != "Completed" || !strings.Contains(state.Message, "build") {
		t.Fatalf("unexpected pod state: %#v", state)
	}
	logs, err := app.remotePodLogs(client, "metis-build-test")
	if err != nil || !strings.Contains(logs, "build logs") {
		t.Fatalf("remotePodLogs: logs=%q err=%v", logs, err)
	}
}

func TestHarborProjectCreationAndPrune(t *testing.T) {
	harbor := fakeHarborServer(t, false)
	app := &App{settings: Settings{
		HarborAPIBase:  harbor.URL + "/api/v2.0",
		HarborUsername: "admin",
		HarborPassword: "pw",
		HarborProject:  "metis",
		HarborRegistry: "registry.example",
	}, metrics: NewMetrics()}
	if got := app.artifactRepo("titan-15"); got != "registry.example/metis/titan-15" {
		t.Fatalf("artifactRepo = %q", got)
	}
	if err := app.ensureHarborProject(); err != nil {
		t.Fatalf("ensureHarborProject: %v", err)
	}
	if err := app.pruneHarborArtifacts("titan-15", 1); err != nil {
		t.Fatalf("pruneHarborArtifacts: %v", err)
	}
}

func TestKubeJSONAndDeleteRequests(t *testing.T) {
	kube := fakeKubeServer(t)
	client := kubeClientFactoryForURL(kube.URL, kube.Client())
	var payload map[string]any
	if err := client.jsonRequest(http.MethodGet, "/api/v1/nodes", nil, &payload); err != nil {
		t.Fatalf("jsonRequest: %v", err)
	}
	if err := client.deleteRequest("/api/v1/nodes/titan-15"); err != nil {
		t.Fatalf("deleteRequest: %v", err)
	}
}

func TestBuildStageAndArchiveHelpers(t *testing.T) {
	if got := remoteArtifactNoteForTest(t); got != "registry.example/metis/titan-15:latest" {
		t.Fatalf("remoteArtifactNote = %q", got)
	}
}

func waitForJobState(t *testing.T, app *App, id string, want JobStatus) {
	t.Helper()
	deadline := time.Now().Add(5 * time.Second)
	for time.Now().Before(deadline) {
		if got := app.job(id); got != nil {
			if got.Status == want {
				return
			}
			if got.Status == JobError {
				t.Fatalf("job %s failed: %s", id, got.Error)
			}
		}
		time.Sleep(10 * time.Millisecond)
	}
	t.Fatalf("job %s never reached state %s", id, want)
}

func installKubeFactory(t *testing.T, srv *httptest.Server) {
	t.Helper()
	orig := kubeClientFactory
	kubeClientFactory = func() (*kubeClient, error) {
		return &kubeClient{baseURL: srv.URL, token: "tok", client: srv.Client()}, nil
	}
	t.Cleanup(func() {
		kubeClientFactory = orig
	})
}

func kubeClientFactoryForURL(baseURL string, client *http.Client) *kubeClient {
	return &kubeClient{baseURL: baseURL, token: "tok", client: client}
}

func fakeKubeServer(t *testing.T) *httptest.Server {
	t.Helper()
	return httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		switch {
		case r.Method == http.MethodGet && r.URL.Path == "/api/v1/nodes":
			_ = json.NewEncoder(w).Encode(map[string]any{
				"items": []any{
					map[string]any{
						"metadata": map[string]any{
							"name": "titan-22",
							"labels": map[string]string{
								"kubernetes.io/arch":             "arm64",
								"hardware":                       "rpi5",
								"node-role.kubernetes.io/worker": "true",
							},
						},
						"spec": map[string]any{"unschedulable": false},
					},
				},
			})
		case r.Method == http.MethodGet && r.URL.Path == "/api/v1/namespaces/maintenance/pods":
			_ = json.NewEncoder(w).Encode(map[string]any{"items": []any{}})
		case r.Method == http.MethodPost && strings.Contains(r.URL.Path, "/pods"):
			w.WriteHeader(http.StatusCreated)
		case r.Method == http.MethodDelete && strings.Contains(r.URL.Path, "/pods/"):
			w.WriteHeader(http.StatusOK)
		case r.Method == http.MethodDelete && strings.Contains(r.URL.Path, "/nodes/"):
			w.WriteHeader(http.StatusOK)
		case r.Method == http.MethodGet && strings.Contains(r.URL.Path, "/pods/") && strings.HasSuffix(r.URL.Path, "/log"):
			_, _ = w.Write([]byte("build logs from kubelet"))
		case r.Method == http.MethodGet && strings.Contains(r.URL.Path, "/pods/"):
			podName := filepath.Base(strings.TrimSuffix(r.URL.Path, "/log"))
			message := `{}`
			switch {
			case strings.Contains(podName, "devices"):
				message = `{"devices":[{"name":"sdz","path":"/dev/sdz","model":"Micro SD","transport":"usb","type":"disk","removable":true,"hotplug":true,"size_bytes":32000000000},{"name":"tmp","path":"hosttmp:///var/tmp/metis-flash-test","model":"Host scratch","transport":"test","type":"file","note":"Test-only host write target under /var/tmp/metis-flash-test","size_bytes":1}]}`
			case strings.Contains(podName, "build"):
				message = `{"local_path":"/workspace/build/titan-15.img.xz","compressed":true,"size_bytes":1234,"build_tag":"build-1"}`
			case strings.Contains(podName, "flash"):
				message = `{"dest_path":"/var/tmp/metis-flash-test/titan-15.img","verified":true,"verification_kind":"image-file","verification_summary":"Verified image layout at /var/tmp/metis-flash-test/titan-15.img; boot and writable partitions are present."}`
			}
			_ = json.NewEncoder(w).Encode(map[string]any{
				"metadata": map[string]any{"name": podName},
				"status": map[string]any{
					"phase":   "Succeeded",
					"message": message,
					"reason":  "Completed",
				},
			})
		default:
			http.NotFound(w, r)
		}
	}))
}

func fakeHarborServer(t *testing.T, projectExists bool) *httptest.Server {
	t.Helper()
	return httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		switch {
		case r.Method == http.MethodGet && strings.HasPrefix(r.URL.Path, "/api/v2.0/projects"):
			if projectExists {
				_ = json.NewEncoder(w).Encode([]map[string]string{{"name": "metis"}})
				return
			}
			_ = json.NewEncoder(w).Encode([]map[string]string{})
		case r.Method == http.MethodPost && r.URL.Path == "/api/v2.0/projects":
			w.WriteHeader(http.StatusCreated)
		case r.Method == http.MethodGet && strings.Contains(r.URL.Path, "/artifacts"):
			_ = json.NewEncoder(w).Encode([]map[string]any{
				{"digest": "sha256:aaa", "push_time": "2026-04-01T10:00:00Z"},
				{"digest": "sha256:bbb", "push_time": "2026-04-01T09:00:00Z"},
			})
		case r.Method == http.MethodDelete && strings.Contains(r.URL.Path, "/artifacts/"):
			w.WriteHeader(http.StatusAccepted)
		default:
			http.NotFound(w, r)
		}
	}))
}

func remoteArtifactNoteForTest(t *testing.T) string {
	t.Helper()
	app := &App{
		settings: Settings{HarborRegistry: "registry.example", HarborProject: "metis"},
		artifactStore: map[string]ArtifactSummary{
			"titan-15": {Node: "titan-15", Ref: "registry.example/metis/titan-15:latest"},
		},
	}
	return app.remoteArtifactNote("titan-15")
}
