package platform

import (
	"bufio"
	"context"
	"fmt"
	"net/http"
	"net/http/httptest"
	"strings"
	"testing"
	"time"
)

func TestProxyForwardsAPIWithBackendHost(t *testing.T) {
	var gotPath, gotHost, gotAuth string
	backend := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		gotPath = r.URL.Path
		gotHost = r.Host
		gotAuth = r.Header.Get("Authorization")
		w.WriteHeader(http.StatusOK)
		fmt.Fprint(w, "clip-body")
	}))
	defer backend.Close()

	proxy, err := NewAPIProxy(backend.URL)
	if err != nil {
		t.Fatalf("NewAPIProxy: %v", err)
	}

	req := httptest.NewRequest(http.MethodGet, "/api/clipboard", nil)
	req.Header.Set("Authorization", "Bearer tok-123")
	rec := httptest.NewRecorder()
	proxy.ServeHTTP(rec, req)

	if rec.Code != http.StatusOK {
		t.Fatalf("status: got %d, want 200", rec.Code)
	}
	if gotPath != "/api/clipboard" {
		t.Errorf("backend path: got %q", gotPath)
	}
	// Host must be rewritten to the backend (TLS SNI + vhost routing), not the
	// WebView's synthetic origin.
	if gotHost != backendHost(backend.URL) {
		t.Errorf("backend Host header: got %q, want %q", gotHost, backendHost(backend.URL))
	}
	if gotAuth != "Bearer tok-123" {
		t.Errorf("Authorization not forwarded: got %q", gotAuth)
	}
	if body := rec.Body.String(); body != "clip-body" {
		t.Errorf("body: got %q", body)
	}
}

func TestProxyNonAPIReturns404(t *testing.T) {
	backend := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
		t.Error("backend should not be hit for non-/api path")
		w.WriteHeader(http.StatusOK)
	}))
	defer backend.Close()

	proxy, err := NewAPIProxy(backend.URL)
	if err != nil {
		t.Fatalf("NewAPIProxy: %v", err)
	}

	rec := httptest.NewRecorder()
	proxy.ServeHTTP(rec, httptest.NewRequest(http.MethodGet, "/settings", nil))
	if rec.Code != http.StatusNotFound {
		t.Errorf("non-/api status: got %d, want 404", rec.Code)
	}
}

func TestProxyForwardsPUT(t *testing.T) {
	var gotMethod, gotBody string
	backend := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		gotMethod = r.Method
		buf := make([]byte, r.ContentLength)
		_, _ = r.Body.Read(buf)
		gotBody = string(buf)
		w.WriteHeader(http.StatusNoContent)
	}))
	defer backend.Close()

	proxy, _ := NewAPIProxy(backend.URL)
	req := httptest.NewRequest(http.MethodPut, "/api/clipboard", strings.NewReader("hello"))
	rec := httptest.NewRecorder()
	proxy.ServeHTTP(rec, req)

	if gotMethod != http.MethodPut {
		t.Errorf("method: got %q", gotMethod)
	}
	if gotBody != "hello" {
		t.Errorf("body: got %q", gotBody)
	}
}

// TestProxyStreamsSSE checks that the proxy relays an event-stream incrementally
// rather than buffering until the backend closes the connection. The backend
// holds the connection open after the first frame; the client must see that
// frame before the deadline.
func TestProxyStreamsSSE(t *testing.T) {
	release := make(chan struct{})
	backend := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
		w.Header().Set("Content-Type", "text/event-stream")
		w.WriteHeader(http.StatusOK)
		fmt.Fprint(w, "event: clipboard:update\ndata: {}\n\n")
		w.(http.Flusher).Flush()
		<-release // keep the stream open like a real SSE channel
	}))
	defer backend.Close()
	defer close(release)

	proxy, _ := NewAPIProxy(backend.URL)
	srv := httptest.NewServer(proxy)
	defer srv.Close()

	ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
	defer cancel()
	req, _ := http.NewRequestWithContext(ctx, http.MethodGet, srv.URL+"/api/hub/events", nil)
	resp, err := http.DefaultClient.Do(req)
	if err != nil {
		t.Fatalf("GET sse: %v", err)
	}
	defer resp.Body.Close()

	done := make(chan string, 1)
	go func() {
		line, _ := bufio.NewReader(resp.Body).ReadString('\n')
		done <- line
	}()

	select {
	case line := <-done:
		if !strings.Contains(line, "clipboard:update") {
			t.Errorf("first streamed frame: got %q", line)
		}
	case <-time.After(1500 * time.Millisecond):
		t.Fatal("SSE frame did not arrive incrementally (proxy buffered the stream)")
	}
}

func backendHost(rawURL string) string {
	// httptest URLs are http://host:port — strip the scheme.
	return strings.TrimPrefix(rawURL, "http://")
}
