package api

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

	"crypto-miner-server/internal/builder"
	"crypto-miner-server/internal/db"
	"crypto-miner-server/internal/pool"
)

type mockConfigProvider struct {
	raw json.RawMessage
}

func (m *mockConfigProvider) GetConfigJSON() json.RawMessage {
	if len(m.raw) == 0 {
		return json.RawMessage(`{"port":8989}`)
	}
	return m.raw
}

func (m *mockConfigProvider) UpdateConfigFromJSON(data json.RawMessage) error {
	m.raw = data
	return nil
}

func newTestRouter(t *testing.T) (http.Handler, string) {
	t.Helper()
	dataDir := t.TempDir()
	database, err := db.New(dataDir)
	if err != nil {
		t.Fatalf("db: %v", err)
	}
	t.Cleanup(func() { database.Close() })

	wsHub := NewWSHub(database)
	cfg := &mockConfigProvider{}
	configHandler := NewConfigHandler(database, cfg)
	aiHandler := NewAIHandler(database)
	fleetHandler := NewFleetHandler(database, wsHub, aiHandler, nil, nil, pool.Config{})
	builderHandler := builder.NewHandler(database, dataDir, "", dataDir)
	blueprintHandler := NewBlueprintHandler(dataDir)

	webRoot := filepath.Join(dataDir, "webroot")
	_ = os.MkdirAll(webRoot, 0755)
	_ = os.WriteFile(filepath.Join(webRoot, "index.html"), []byte("<html><body>AetherForge</body></html>"), 0644)

	dropperHandler := NewDropperHandler(database, nil)
	return NewRouter(database, wsHub, configHandler, builderHandler, blueprintHandler, aiHandler, fleetHandler, dropperHandler, webRoot, dataDir, nil), dataDir
}

func TestHealthIsPublic(t *testing.T) {
	router, _ := newTestRouter(t)
	req := httptest.NewRequest(http.MethodGet, "/api/v1/health", nil)
	rec := httptest.NewRecorder()
	router.ServeHTTP(rec, req)
	if rec.Code != http.StatusOK {
		t.Fatalf("health status=%d body=%s", rec.Code, rec.Body.String())
	}
	var body map[string]string
	if err := json.Unmarshal(rec.Body.Bytes(), &body); err != nil {
		t.Fatal(err)
	}
	if body["status"] != "ok" {
		t.Fatalf("unexpected health: %v", body)
	}
}

func TestConfigRequiresAuth(t *testing.T) {
	router, _ := newTestRouter(t)
	req := httptest.NewRequest(http.MethodGet, "/api/v1/config", nil)
	rec := httptest.NewRecorder()
	router.ServeHTTP(rec, req)
	if rec.Code != http.StatusUnauthorized {
		t.Fatalf("expected 401, got %d", rec.Code)
	}
}

func TestConfigWithValidAuth(t *testing.T) {
	router, _ := newTestRouter(t)
	req := httptest.NewRequest(http.MethodGet, "/api/v1/config", nil)
	req.SetBasicAuth("drjones", "czapiewski")
	rec := httptest.NewRecorder()
	router.ServeHTTP(rec, req)
	if rec.Code != http.StatusOK {
		t.Fatalf("expected 200, got %d body=%s", rec.Code, rec.Body.String())
	}
}

func TestAgentsListRequiresAuth(t *testing.T) {
	router, _ := newTestRouter(t)
	req := httptest.NewRequest(http.MethodGet, "/api/v1/agents", nil)
	rec := httptest.NewRecorder()
	router.ServeHTTP(rec, req)
	if rec.Code != http.StatusUnauthorized {
		t.Fatalf("expected 401, got %d", rec.Code)
	}
}

func TestAgentsListAuthedEmpty(t *testing.T) {
	router, _ := newTestRouter(t)
	req := httptest.NewRequest(http.MethodGet, "/api/v1/agents", nil)
	req.SetBasicAuth("drjones", "czapiewski")
	rec := httptest.NewRecorder()
	router.ServeHTTP(rec, req)
	if rec.Code != http.StatusOK {
		t.Fatalf("expected 200, got %d", rec.Code)
	}
	var agents []json.RawMessage
	if err := json.Unmarshal(rec.Body.Bytes(), &agents); err != nil {
		t.Fatal(err)
	}
	if len(agents) != 0 {
		t.Fatalf("expected empty fleet, got %d", len(agents))
	}
}

func TestArtifactDownloadRejectsTraversal(t *testing.T) {
	router, dataDir := newTestRouter(t)
	buildID := "test-build-id"
	buildDir := filepath.Join(dataDir, "builds", buildID)
	if err := os.MkdirAll(buildDir, 0755); err != nil {
		t.Fatal(err)
	}
	req := httptest.NewRequest(http.MethodGet, "/api/v1/builds/"+buildID+"/artifact/..%2F..%2Fsecret.txt", nil)
	rec := httptest.NewRecorder()
	router.ServeHTTP(rec, req)
	if rec.Code != http.StatusBadRequest && rec.Code != http.StatusNotFound {
		t.Fatalf("expected rejection, got %d body=%s", rec.Code, rec.Body.String())
	}
}

func TestSPAServesIndex(t *testing.T) {
	router, _ := newTestRouter(t)
	req := httptest.NewRequest(http.MethodGet, "/dashboard", nil)
	rec := httptest.NewRecorder()
	router.ServeHTTP(rec, req)
	if rec.Code != http.StatusOK {
		t.Fatalf("expected 200, got %d", rec.Code)
	}
	if !contains(rec.Body.String(), "AetherForge") {
		t.Fatalf("expected SPA fallback html")
	}
}

func contains(s, sub string) bool {
	return len(s) >= len(sub) && (s == sub || len(sub) == 0 || indexOf(s, sub) >= 0)
}

func indexOf(s, sub string) int {
	for i := 0; i+len(sub) <= len(s); i++ {
		if s[i:i+len(sub)] == sub {
			return i
		}
	}
	return -1
}

func TestStatsLimitCapped(t *testing.T) {
	router, _ := newTestRouter(t)
	req := httptest.NewRequest(http.MethodGet, "/api/v1/agents/nope/stats?limit=999999", nil)
	req.SetBasicAuth("drjones", "czapiewski")
	rec := httptest.NewRecorder()
	router.ServeHTTP(rec, req)
	// Agent may not exist — 404 is fine; we only care handler doesn't 500 on huge limit
	if rec.Code == http.StatusInternalServerError {
		t.Fatalf("limit cap caused server error: %s", rec.Body.String())
	}
}
