package service

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

func TestFlashOnlyWorkflowRequiresPublishedArtifact(t *testing.T) {
	kube := remoteWorkflowKubeServer(t, remoteKubeOptions{})
	installKubeFactory(t, kube)
	app := remoteTestApp(t, nil)

	if _, err := app.Flash("titan-15", "titan-22", "/dev/sdz"); err == nil || !strings.Contains(err.Error(), "run a build first") {
		t.Fatalf("expected missing artifact error, got %v", err)
	}
}

func TestFlashOnlyWorkflowCompletesAndRecordsVerification(t *testing.T) {
	kube := remoteWorkflowKubeServer(t, remoteKubeOptions{flashMessage: `{"dest_path":"/dev/sdz","verified":true,"verification_kind":"block-device","verification_summary":"Verified /dev/sdz; system-boot and writable are present and the boot files look correct.","boot_partition":"/dev/sdz1","root_partition":"/dev/sdz2","boot_label":"system-boot","root_label":"writable","boot_fstype":"vfat","root_fstype":"ext4","checked_files":["config.txt","cmdline.txt","boot.scr"]}`})
	installKubeFactory(t, kube)
	app := remoteTestApp(t, nil)
	app.artifactStore["titan-15"] = ArtifactSummary{Node: "titan-15", Ref: "registry.example/metis/titan-15:latest"}

	job, err := app.Flash("titan-15", "titan-22", "/dev/sdz")
	if err != nil {
		t.Fatalf("Flash: %v", err)
	}
	waitForJobState(t, app, job.ID, JobDone)

	got := app.job(job.ID)
	if got == nil || got.Kind != "flash" || got.Status != JobDone {
		t.Fatalf("unexpected flash job: %#v", got)
	}
	if !strings.Contains(got.Message, "Move the card into titan-15") {
		t.Fatalf("unexpected completion message: %#v", got)
	}
	if got.ProgressPct != 100 {
		t.Fatalf("expected complete flash progress, got %#v", got)
	}

	events := app.recentEvents(5)
	if len(events) == 0 || events[0].Kind != "image.flash" {
		t.Fatalf("expected flash event, got %#v", events)
	}
	if verified, ok := events[0].Details["verified"].(bool); !ok || !verified {
		t.Fatalf("expected verified flash details, got %#v", events[0].Details)
	}
	if kind, ok := events[0].Details["verification_kind"].(string); !ok || kind != "block-device" {
		t.Fatalf("expected block-device verification, got %#v", events[0].Details)
	}
	if bootPartition, ok := events[0].Details["boot_partition"].(string); !ok || bootPartition != "/dev/sdz1" {
		t.Fatalf("expected boot partition details, got %#v", events[0].Details)
	}
}

func TestHandleFlashRouteStartsFlashOnlyJob(t *testing.T) {
	kube := remoteWorkflowKubeServer(t, remoteKubeOptions{})
	installKubeFactory(t, kube)
	app := remoteTestApp(t, nil)
	app.artifactStore["titan-15"] = ArtifactSummary{Node: "titan-15", Ref: "registry.example/metis/titan-15:latest"}
	handler := app.Handler()

	req := httptest.NewRequest(http.MethodPost, "/api/jobs/flash", strings.NewReader(`{"node":"titan-15","host":"titan-22","device":"/dev/sdz"}`))
	req.Header.Set("Content-Type", "application/json")
	req.Header.Set("X-Auth-Request-User", "brad")
	req.Header.Set("X-Auth-Request-Groups", "admin")
	resp := httptest.NewRecorder()
	handler.ServeHTTP(resp, req)
	if resp.Code != http.StatusAccepted {
		t.Fatalf("flash route response: %d %s", resp.Code, resp.Body.String())
	}

	var job Job
	if err := json.Unmarshal(resp.Body.Bytes(), &job); err != nil {
		t.Fatalf("decode flash job: %v", err)
	}
	waitForJobState(t, app, job.ID, JobDone)
	if got := app.job(job.ID); got == nil || got.Kind != "flash" {
		t.Fatalf("expected flash job, got %#v", got)
	}
}

func TestFlashAndReplaceDefaultHostBranches(t *testing.T) {
	kube := remoteWorkflowKubeServer(t, remoteKubeOptions{})
	harbor := fakeHarborServer(t, true)
	installKubeFactory(t, kube)
	app := remoteTestApp(t, harbor)
	app.artifactStore["titan-15"] = ArtifactSummary{Node: "titan-15", Ref: "registry.example/metis/titan-15:latest"}

	flashJob, err := app.Flash("titan-15", "", hostTmpDevicePath)
	if err != nil {
		t.Fatalf("Flash default host: %v", err)
	}
	waitForJobState(t, app, flashJob.ID, JobDone)
	if got := app.job(flashJob.ID); got == nil || got.Host != "titan-22" || !strings.Contains(got.Message, "host /tmp") {
		t.Fatalf("unexpected flash default host job: %#v", got)
	}

	replaceJob, err := app.Replace("titan-15", "", hostTmpDevicePath)
	if err != nil {
		t.Fatalf("Replace default host: %v", err)
	}
	waitForJobState(t, app, replaceJob.ID, JobDone)
	if got := app.job(replaceJob.ID); got == nil || got.Host != "titan-22" {
		t.Fatalf("unexpected replace default host job: %#v", got)
	}
}

func TestFlashRejectsDuplicateActiveNodeJob(t *testing.T) {
	kube := remoteWorkflowKubeServer(t, remoteKubeOptions{})
	installKubeFactory(t, kube)
	app := remoteTestApp(t, nil)
	app.artifactStore["titan-15"] = ArtifactSummary{Node: "titan-15", Ref: "registry.example/metis/titan-15:latest"}
	app.newJob("flash", "titan-15", "titan-22", "/dev/sdz")

	if _, err := app.Flash("titan-15", "titan-22", "/dev/sdz"); err == nil || !strings.Contains(err.Error(), "already has an active") {
		t.Fatalf("expected flash conflict, got %v", err)
	}
}

func TestFlashArtifactUpdatesProgressWhenVerificationSummaryIsMissing(t *testing.T) {
	kube := remoteWorkflowKubeServer(t, remoteKubeOptions{flashMessage: `{"dest_path":"/dev/sdz","size_bytes":4096,"verified":true}`})
	installKubeFactory(t, kube)
	app := remoteTestApp(t, nil)
	job := app.newJob("flash", "titan-15", "titan-22", "/dev/sdz")

	result, err := app.flashArtifact(job.ID, "registry.example/metis/titan-15")
	if err != nil {
		t.Fatalf("flashArtifact: %v", err)
	}
	if result.SizeBytes != 4096 {
		t.Fatalf("unexpected flash result: %#v", result)
	}
	got := app.job(job.ID)
	if got == nil || got.Written != 4096 || got.Total != 4096 || !strings.Contains(got.Message, "Verified the latest image write") {
		t.Fatalf("unexpected flash artifact state: %#v", got)
	}
}

func TestFlashOnlyWorkflowFailsVerification(t *testing.T) {
	kube := remoteWorkflowKubeServer(t, remoteKubeOptions{flashMessage: `{"dest_path":"/dev/sdz","verified":false}`})
	installKubeFactory(t, kube)
	app := remoteTestApp(t, nil)
	app.artifactStore["titan-15"] = ArtifactSummary{Node: "titan-15", Ref: "registry.example/metis/titan-15:latest"}

	job, err := app.Flash("titan-15", "titan-22", "/dev/sdz")
	if err != nil {
		t.Fatalf("Flash: %v", err)
	}
	deadline := time.Now().Add(5 * time.Second)
	for time.Now().Before(deadline) {
		if got := app.job(job.ID); got != nil && got.Status == JobError {
			if !strings.Contains(got.Error, "verification did not succeed") {
				t.Fatalf("unexpected flash verification error: %#v", got)
			}
			return
		}
		time.Sleep(10 * time.Millisecond)
	}
	t.Fatalf("flash job %s never failed verification: %#v", job.ID, app.job(job.ID))
}

func TestBuildFlashAndReplaceRejectInvalidInputs(t *testing.T) {
	kube := remoteWorkflowKubeServer(t, remoteKubeOptions{})
	installKubeFactory(t, kube)
	app := remoteTestApp(t, nil)
	app.artifactStore["titan-15"] = ArtifactSummary{Node: "titan-15", Ref: "registry.example/metis/titan-15:latest"}

	if _, err := app.Build("missing-node"); err == nil || !strings.Contains(err.Error(), "missing-node") {
		t.Fatalf("expected build inventory error, got %v", err)
	}
	if _, err := app.Flash("titan-15", "titan-22", "/dev/sda"); err == nil || !strings.Contains(err.Error(), "not a current flash candidate") {
		t.Fatalf("expected flash device validation error, got %v", err)
	}
	if _, err := app.Replace("titan-15", "titan-22", "/dev/sda"); err == nil || !strings.Contains(err.Error(), "not a current flash candidate") {
		t.Fatalf("expected replace device validation error, got %v", err)
	}
}
