package api_test

import (
	"net/http"
	"testing"
)

// TestConfigRuleCRUD exercises the full config-rule REST surface: list, create,
// get, update, versioning (list/get/promote/fork), check, preview, and delete,
// plus the principal error paths.
func TestConfigRuleCRUD(t *testing.T) {
	srv, teardown := setup(t)
	defer teardown()

	// A set the rule (and preview) can bind to.
	resp := postJSON(t, srv, "/api/v1/config/sets", map[string]any{
		"name": "S",
		"parameters": []map[string]any{
			{"key": "voltage", "ds": "stub", "signal": "setpoint", "type": "float64", "default": 12.0},
		},
	})
	assertStatus(t, resp, http.StatusCreated)
	var set struct {
		ID string `json:"id"`
	}
	readJSON(t, resp, &set)

	// Empty list initially.
	resp = get(t, srv, "/api/v1/config/rules")
	assertStatus(t, resp, http.StatusOK)
	var list []map[string]any
	readJSON(t, resp, &list)
	if len(list) != 0 {
		t.Fatalf("expected no rules, got %d", len(list))
	}

	// Create.
	resp = postJSON(t, srv, "/api/v1/config/rules", map[string]any{
		"name":   "cap",
		"setId":  set.ID,
		"source": "voltage: <=24",
	})
	assertStatus(t, resp, http.StatusCreated)
	var rule struct {
		ID      string `json:"id"`
		Version int    `json:"version"`
	}
	readJSON(t, resp, &rule)
	if rule.ID == "" {
		t.Fatal("rule missing id")
	}

	// Create with invalid CUE → 400.
	resp = postJSON(t, srv, "/api/v1/config/rules", map[string]any{
		"name":   "bad",
		"setId":  set.ID,
		"source": "voltage: <=",
	})
	assertStatus(t, resp, http.StatusBadRequest)

	// Create with malformed JSON → 400.
	resp = postRaw(t, srv, "/api/v1/config/rules", "application/json", []byte(`{not json`))
	assertStatus(t, resp, http.StatusBadRequest)

	// Get.
	resp = get(t, srv, "/api/v1/config/rules/"+rule.ID)
	assertStatus(t, resp, http.StatusOK)
	resp.Body.Close()

	// Get missing → 404.
	resp = get(t, srv, "/api/v1/config/rules/nope")
	assertStatus(t, resp, http.StatusNotFound)

	// Update (bumps version, creates backup).
	resp = putRaw(t, srv, "/api/v1/config/rules/"+rule.ID, "application/json",
		[]byte(`{"name":"cap2","setId":"`+set.ID+`","source":"voltage: <=30"}`))
	assertStatus(t, resp, http.StatusOK)

	// List versions.
	resp = get(t, srv, "/api/v1/config/rules/"+rule.ID+"/versions")
	assertStatus(t, resp, http.StatusOK)
	var versions []map[string]any
	readJSON(t, resp, &versions)
	if len(versions) < 2 {
		t.Fatalf("expected >=2 rule versions, got %d", len(versions))
	}

	// Get v1.
	resp = get(t, srv, "/api/v1/config/rules/"+rule.ID+"/versions/1")
	assertStatus(t, resp, http.StatusOK)
	resp.Body.Close()

	// Bad version path → 400.
	resp = get(t, srv, "/api/v1/config/rules/"+rule.ID+"/versions/xyz")
	assertStatus(t, resp, http.StatusBadRequest)

	// Promote v1.
	resp = postRaw(t, srv, "/api/v1/config/rules/"+rule.ID+"/versions/1/promote", "application/json", nil)
	assertStatus(t, resp, http.StatusOK)

	// Fork v1 → new id.
	resp = postRaw(t, srv, "/api/v1/config/rules/"+rule.ID+"/versions/1/fork", "application/json", nil)
	assertStatus(t, resp, http.StatusCreated)
	var fork struct {
		ID string `json:"id"`
	}
	readJSON(t, resp, &fork)
	if fork.ID == "" || fork.ID == rule.ID {
		t.Errorf("fork id = %q, want fresh id", fork.ID)
	}

	// Check (live validation of an unsaved source against sample values).
	resp = postJSON(t, srv, "/api/v1/config/rules/check", map[string]any{
		"source": "voltage: <=24",
		"values": map[string]any{"voltage": 20.0},
	})
	assertStatus(t, resp, http.StatusOK)
	resp.Body.Close()

	// Check with malformed JSON → 400.
	resp = postRaw(t, srv, "/api/v1/config/rules/check", "application/json", []byte(`{`))
	assertStatus(t, resp, http.StatusBadRequest)

	// Preview against the bound set's live signals.
	resp = postJSON(t, srv, "/api/v1/config/rules/preview", map[string]any{
		"setId":  set.ID,
		"source": "voltage: <=24",
	})
	assertStatus(t, resp, http.StatusOK)
	resp.Body.Close()

	// Preview with an unknown set → 404.
	resp = postJSON(t, srv, "/api/v1/config/rules/preview", map[string]any{
		"setId":  "does-not-exist",
		"source": "voltage: <=24",
	})
	assertStatus(t, resp, http.StatusNotFound)

	// Delete.
	resp = deleteReq(t, srv, "/api/v1/config/rules/"+rule.ID)
	assertStatus(t, resp, http.StatusNoContent)

	// Delete missing → 404.
	resp = deleteReq(t, srv, "/api/v1/config/rules/"+rule.ID)
	assertStatus(t, resp, http.StatusNotFound)
}
