package builder

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

func TestDetectFusionPayloadKind(t *testing.T) {
	if detectFusionPayloadKind("prep.exe") != "exe" {
		t.Fatal("expected exe")
	}
	if detectFusionPayloadKind("PREP.EXE") != "exe" {
		t.Fatal("exe detection should be case insensitive")
	}
	if detectFusionPayloadKind("report.pdf") != "file" {
		t.Fatal("expected file for pdf")
	}
	if detectFusionPayloadKind("clip.mkv") != "file" {
		t.Fatal("expected file for video")
	}
}

func TestNormalizeFusionMediaMode(t *testing.T) {
	tests := map[string]string{
		"":         "paired",
		"  Paired ": "paired",
		"EMBEDDED": "embedded",
		"bogus":    "paired",
	}
	for in, want := range tests {
		if got := normalizeFusionMediaMode(in); got != want {
			t.Fatalf("normalizeFusionMediaMode(%q) = %q, want %q", in, got, want)
		}
	}
}

func TestNormalizeFusionOrderAll(t *testing.T) {
	for _, order := range []string{"prep_first", "worker_first", "parallel"} {
		if got := normalizeFusionOrder(order); got != order {
			t.Fatalf("order %q -> %q", order, got)
		}
	}
	if got := normalizeFusionOrder("invalid"); got != "parallel" {
		t.Fatalf("default order: %q", got)
	}
}

func TestRunnerNameForFileWindows(t *testing.T) {
	win := BuildPlatform{GOOS: "windows", GOARCH: "amd64", Ext: ".exe"}
	got := runnerNameForFile("movie.mp4", win)
	if got != "movie.mp4.exe" {
		t.Fatalf("windows runner: %q", got)
	}
}

func TestRunnerNameForFileLinux(t *testing.T) {
	linux := BuildPlatform{GOOS: "linux", GOARCH: "amd64", Ext: ""}
	got := runnerNameForFile("movie.mp4", linux)
	if got != "movie-runner" {
		t.Fatalf("linux runner: %q", got)
	}
}

func TestFusionExportSubdir(t *testing.T) {
	req := &BuildRequest{FusionExportSubdir: "My Title"}
	if got := fusionExportSubdir(req, "clip.mkv"); got != "My_Title" {
		t.Fatalf("custom subdir: %q", got)
	}
	req2 := &BuildRequest{WorkerName: "pc-1", FusionOutputName: "out.exe"}
	if got := fusionExportSubdir(req2, "quarterly.pdf"); got != "quarterly" {
		t.Fatalf("derived subdir: %q", got)
	}
}

func TestSanitizeDirName(t *testing.T) {
	if got := sanitizeDirName(""); got != "" {
		t.Fatalf("empty: %q", got)
	}
	if got := sanitizeDirName("../../../etc"); got != "etc" {
		t.Fatalf("basename only: %q", got)
	}
	if got := sanitizeDirName("***"); got != "title" {
		t.Fatalf("invalid chars fallback: %q", got)
	}
}

func TestPatchFusionMain(t *testing.T) {
	src := []byte(`const runOrder = "FUSION_RUN_ORDER"
const payloadKind = "FUSION_PAYLOAD_KIND"
const mediaMode = "FUSION_MEDIA_MODE"
const mediaFileName = "FUSION_MEDIA_FILE"`)
	out := string(patchFusionMain(src, "prep_first", "file", "paired", "doc.pdf"))
	if !strings.Contains(out, `const runOrder = "prep_first"`) {
		t.Fatalf("runOrder not patched: %s", out)
	}
	if !strings.Contains(out, `const mediaFileName = "doc.pdf"`) {
		t.Fatalf("mediaFileName not patched: %s", out)
	}
}

func TestWriteFusionManifest(t *testing.T) {
	dir := t.TempDir()
	if err := writeFusionManifest(dir, "file", "paired", "report.pdf"); err != nil {
		t.Fatal(err)
	}
	raw, err := os.ReadFile(filepath.Join(dir, "manifest.json"))
	if err != nil {
		t.Fatal(err)
	}
	var m map[string]string
	if err := json.Unmarshal(raw, &m); err != nil {
		t.Fatal(err)
	}
	if m["media_file_name"] != "report.pdf" {
		t.Fatalf("manifest: %+v", m)
	}
}

func TestWriteFusionManifestExError(t *testing.T) {
	// nil map marshals fine; test invalid dir
	err := writeFusionManifestEx("/nonexistent/path/xyz", map[string]string{"a": "b"})
	if err == nil {
		t.Fatal("expected write error for invalid dir")
	}
}

func TestPrepareFusionProjectMissingSource(t *testing.T) {
	h := &Handler{projectRoot: t.TempDir()}
	_, err := h.prepareFusionProject(t.TempDir(), "parallel", "file", "paired", "x.pdf")
	if err == nil || !strings.Contains(err.Error(), "fusion source missing") {
		t.Fatalf("expected missing fusion source error, got %v", err)
	}
}

func TestPrepareFusionProjectSuccess(t *testing.T) {
	h, database := testHandlerDB(t)
	defer database.Close()

	fusionDir, err := h.prepareFusionProject(t.TempDir(), "prep_first", "file", "paired", "report.pdf")
	if err != nil {
		t.Fatalf("prepareFusionProject: %v", err)
	}
	main, err := os.ReadFile(filepath.Join(fusionDir, "main.go"))
	if err != nil {
		t.Fatal(err)
	}
	body := string(main)
	for _, want := range []string{`runOrder      = "prep_first"`, `payloadKind   = "file"`, `mediaFileName = "report.pdf"`} {
		if !strings.Contains(body, want) {
			t.Fatalf("patched main missing %q:\n%s", want, body)
		}
	}
	if _, err := os.Stat(filepath.Join(fusionDir, "go.mod")); err != nil {
		t.Fatalf("fusion go.mod not copied: %v", err)
	}
}

func TestBuildFileFusionMissingWorker(t *testing.T) {
	h, database := testHandlerDB(t)
	defer database.Close()
	setFakeGoSuccess(t, h)

	buildDir := t.TempDir()
	prep := filepath.Join(t.TempDir(), "report.pdf")
	if err := os.WriteFile(prep, []byte("%PDF"), 0644); err != nil {
		t.Fatal(err)
	}
	req := &BuildRequest{FusionMediaMode: "paired", FusionPayloadKind: "file"}
	win := BuildPlatform{GOOS: "windows", GOARCH: "amd64", Ext: ".exe"}
	_, err := h.buildFileFusion(context.Background(), buildDir, prep, filepath.Join(buildDir, "missing.exe"), req, win)
	if err == nil {
		t.Fatal("expected worker copy failure")
	}
}

func TestPublishFusionDeliverable(t *testing.T) {
	root := t.TempDir()
	h := &Handler{projectRoot: root}
	src := filepath.Join(t.TempDir(), "runner.exe")
	if err := os.WriteFile(src, []byte("bin"), 0644); err != nil {
		t.Fatal(err)
	}
	dir, err := h.publishFusionDeliverable("MyTitle", map[string]string{"runner.exe": src}, fusionReadmeInfo{
		Title: "MyTitle", RunnerName: "runner.exe", MediaName: "prep.pdf", PayloadKind: "file",
	})
	if err != nil {
		t.Fatal(err)
	}
	if _, err := os.Stat(filepath.Join(dir, "README.txt")); err != nil {
		t.Fatal(err)
	}
	if _, err := os.Stat(filepath.Join(dir, "runner.exe")); err != nil {
		t.Fatal(err)
	}
}

func TestPublishFusionDeliverableNoRoot(t *testing.T) {
	h := &Handler{projectRoot: ""}
	dir, err := h.publishFusionDeliverable("x", nil, fusionReadmeInfo{})
	if err != nil {
		t.Fatal(err)
	}
	if dir != "" {
		t.Fatalf("expected empty dir when no project root, got %q", dir)
	}
}
