package api

import (
	"bytes"
	"encoding/json"
	"net/http"
	"net/http/httptest"
	"os"
	"path/filepath"
	"strings"
	"testing"

	"github.com/go-chi/chi/v5"
)

func newTestBlueprintHandler(t *testing.T) (*BlueprintHandler, string) {
	t.Helper()
	dataDir := t.TempDir()
	return NewBlueprintHandler(dataDir), dataDir
}

func TestSanitizeFilename(t *testing.T) {
	if sanitizeFilename("  my preset  ") != "my preset" {
		t.Fatalf("trim failed: %q", sanitizeFilename("  my preset  "))
	}
	if sanitizeFilename("../../../etc/passwd") == "" || strings.Contains(sanitizeFilename("../../../etc/passwd"), "..") {
		t.Fatalf("traversal not sanitized: %q", sanitizeFilename("../../../etc/passwd"))
	}
	if sanitizeFilename("bad/name") != "badname" {
		t.Fatalf("slashes removed: %q", sanitizeFilename("bad/name"))
	}
}

func TestBlueprintListEmpty(t *testing.T) {
	h, _ := newTestBlueprintHandler(t)
	req := httptest.NewRequest(http.MethodGet, "/api/v1/blueprints", nil)
	rec := httptest.NewRecorder()
	h.ServeHTTP(rec, req)
	if rec.Code != http.StatusOK {
		t.Fatalf("status %d body %s", rec.Code, rec.Body.String())
	}
	var list []BlueprintInfo
	if err := json.Unmarshal(rec.Body.Bytes(), &list); err != nil {
		t.Fatal(err)
	}
	if len(list) != 0 {
		t.Fatalf("expected empty list, got %d", len(list))
	}
}

func TestBlueprintSaveGetDelete(t *testing.T) {
	h, dataDir := newTestBlueprintHandler(t)

	saveBody, _ := json.Marshal(map[string]interface{}{
		"name": "fleet-default",
		"data": map[string]interface{}{"pool_host": "pool.example.com", "threads": 4},
	})
	req := httptest.NewRequest(http.MethodPost, "/api/v1/blueprints", bytes.NewReader(saveBody))
	rec := httptest.NewRecorder()
	h.ServeHTTP(rec, req)
	if rec.Code != http.StatusOK {
		t.Fatalf("save status %d body %s", rec.Code, rec.Body.String())
	}

	filePath := filepath.Join(dataDir, "blueprints", "fleet-default.json")
	if _, err := os.Stat(filePath); err != nil {
		t.Fatalf("blueprint file missing: %v", err)
	}

	req = httptest.NewRequest(http.MethodGet, "/api/v1/blueprints", nil)
	rec = httptest.NewRecorder()
	h.ServeHTTP(rec, req)
	var listed []BlueprintInfo
	if err := json.Unmarshal(rec.Body.Bytes(), &listed); err != nil {
		t.Fatal(err)
	}
	if len(listed) != 1 || listed[0].Name != "fleet-default" {
		t.Fatalf("list after save: %+v", listed)
	}

	r := chi.NewRouter()
	r.Get("/blueprints/{name}", h.GetBlueprint)
	req = httptest.NewRequest(http.MethodGet, "/blueprints/fleet-default", nil)
	rec = httptest.NewRecorder()
	r.ServeHTTP(rec, req)
	if rec.Code != http.StatusOK {
		t.Fatalf("get status %d body %s", rec.Code, rec.Body.String())
	}
	if !strings.Contains(rec.Body.String(), "pool.example.com") {
		t.Fatalf("unexpected blueprint body: %s", rec.Body.String())
	}

	req = httptest.NewRequest(http.MethodDelete, "/api/v1/blueprints?name=fleet-default", nil)
	rec = httptest.NewRecorder()
	h.ServeHTTP(rec, req)
	if rec.Code != http.StatusOK {
		t.Fatalf("delete status %d body %s", rec.Code, rec.Body.String())
	}
	if _, err := os.Stat(filePath); !os.IsNotExist(err) {
		t.Fatal("blueprint file should be removed")
	}
}

func TestBlueprintSaveValidationErrors(t *testing.T) {
	h, _ := newTestBlueprintHandler(t)

	req := httptest.NewRequest(http.MethodPost, "/api/v1/blueprints", bytes.NewReader([]byte(`{"name":"","data":{}}`)))
	rec := httptest.NewRecorder()
	h.ServeHTTP(rec, req)
	if rec.Code != http.StatusBadRequest {
		t.Fatalf("empty name should be 400, got %d", rec.Code)
	}

	req = httptest.NewRequest(http.MethodPost, "/api/v1/blueprints", bytes.NewReader([]byte(`not-json`)))
	rec = httptest.NewRecorder()
	h.ServeHTTP(rec, req)
	if rec.Code != http.StatusBadRequest {
		t.Fatalf("invalid json should be 400, got %d", rec.Code)
	}

	req = httptest.NewRequest(http.MethodPost, "/api/v1/blueprints", bytes.NewReader([]byte(`{"name":"ok","data":}`)))
	rec = httptest.NewRecorder()
	h.ServeHTTP(rec, req)
	if rec.Code != http.StatusBadRequest {
		t.Fatalf("malformed data json should be 400, got %d", rec.Code)
	}
}

func TestBlueprintGetNotFound(t *testing.T) {
	h, _ := newTestBlueprintHandler(t)
	r := chi.NewRouter()
	r.Get("/blueprints/{name}", h.GetBlueprint)
	req := httptest.NewRequest(http.MethodGet, "/blueprints/missing", nil)
	rec := httptest.NewRecorder()
	r.ServeHTTP(rec, req)
	if rec.Code != http.StatusNotFound {
		t.Fatalf("expected 404, got %d", rec.Code)
	}
}

func TestBlueprintDeleteMissingName(t *testing.T) {
	h, _ := newTestBlueprintHandler(t)
	req := httptest.NewRequest(http.MethodDelete, "/api/v1/blueprints", nil)
	rec := httptest.NewRecorder()
	h.ServeHTTP(rec, req)
	if rec.Code != http.StatusBadRequest {
		t.Fatalf("expected 400, got %d", rec.Code)
	}
}

func TestBlueprintMethodNotAllowed(t *testing.T) {
	h, _ := newTestBlueprintHandler(t)
	req := httptest.NewRequest(http.MethodPatch, "/api/v1/blueprints", nil)
	rec := httptest.NewRecorder()
	h.ServeHTTP(rec, req)
	if rec.Code != http.StatusMethodNotAllowed {
		t.Fatalf("expected 405, got %d", rec.Code)
	}
}
