package api

import (
	"context"
	"net/http"
	"net/http/httptest"
	"strings"
	"testing"

	"drivervault/apiserver/internal/config"
	"drivervault/apiserver/internal/ocpp"
	"drivervault/apiserver/internal/pb"
)

// TestOCPPRouteAndControlAuth exercises the real HTTP stack (all middleware
// included) to confirm the OCPP endpoint and the control REST routes are wired,
// and that the charger-auth gate rejects an unknown token. PocketBase is left
// unconfigured, so the successful (charger-connected) path is out of scope here —
// that is covered by the internal/ocpp own/proxy tests.
func TestOCPPRouteAndControlAuth(t *testing.T) {
	s := New(config.Config{UsersCollection: "users"}, pb.New("", "", ""))
	srv := httptest.NewServer(s.Handler())
	defer srv.Close()

	wsBase := "ws" + strings.TrimPrefix(srv.URL, "http")

	// A charger connecting with no OCPP Basic auth is rejected (401).
	if _, err := ocpp.Dial(context.Background(), wsBase+"/ocpp/CP1", []string{"ocpp1.6"}, nil); err == nil ||
		!strings.Contains(err.Error(), "401") {
		t.Fatalf("unauthenticated charger connect: want HTTP 401, got %v", err)
	}

	// A bogus token is unknown to the index (PB unconfigured, so the rebuild is a
	// no-op) and is likewise rejected.
	h := http.Header{}
	h.Set("Authorization", ocpp.BasicAuthHeader("CP1", "bogus-token"))
	if _, err := ocpp.Dial(context.Background(), wsBase+"/ocpp/CP1", []string{"ocpp1.6"}, h); err == nil ||
		!strings.Contains(err.Error(), "401") {
		t.Fatalf("bogus-token charger connect: want HTTP 401, got %v", err)
	}

	// The control REST routes are registered (401 for a missing bearer token, not
	// 404 for an unknown route) — this also proves the {sn}/control,
	// {sn}/control/token and {sn}/{action} patterns don't shadow each other.
	routes := []struct {
		method, path string
	}{
		{http.MethodGet, "/api/integrations/anker-solix/chargers/CP1/control"},
		{http.MethodPost, "/api/integrations/anker-solix/chargers/CP1/control/token"},
		{http.MethodPost, "/api/integrations/anker-solix/chargers/CP1/start"},
	}
	for _, rt := range routes {
		req, _ := http.NewRequest(rt.method, srv.URL+rt.path, nil)
		resp, err := http.DefaultClient.Do(req)
		if err != nil {
			t.Fatalf("%s %s: %v", rt.method, rt.path, err)
		}
		resp.Body.Close()
		if resp.StatusCode != http.StatusUnauthorized {
			t.Errorf("%s %s: status %d, want 401 (route registered, bearer required)", rt.method, rt.path, resp.StatusCode)
		}
	}
}

// TestOCPPRequireTLS confirms a plaintext charger connection is rejected when TLS
// is required, and that a TLS-terminating proxy (X-Forwarded-Proto) satisfies the
// check (falling through to the auth gate).
func TestOCPPRequireTLS(t *testing.T) {
	s := New(config.Config{UsersCollection: "users", OCPPRequireTLS: true}, pb.New("", "", ""))
	srv := httptest.NewServer(s.Handler())
	defer srv.Close()
	wsBase := "ws" + strings.TrimPrefix(srv.URL, "http")

	// Plaintext ws:// with no forwarded-proto → rejected before auth (403).
	if _, err := ocpp.Dial(context.Background(), wsBase+"/ocpp/CP1", []string{"ocpp1.6"}, nil); err == nil ||
		!strings.Contains(err.Error(), "403") {
		t.Fatalf("plaintext connect under require-TLS: want HTTP 403, got %v", err)
	}

	// Behind a TLS-terminating proxy the connection is accepted past the TLS gate
	// and then fails auth (401), proving the gate ordering.
	h := http.Header{}
	h.Set("X-Forwarded-Proto", "https")
	if _, err := ocpp.Dial(context.Background(), wsBase+"/ocpp/CP1", []string{"ocpp1.6"}, h); err == nil ||
		!strings.Contains(err.Error(), "401") {
		t.Fatalf("forwarded-https connect: want HTTP 401 (past TLS gate), got %v", err)
	}
}
