// Unit tests for the Supabase admin HTTP client. The client is a thin
// shim over net/http; coverage lives at the wire-shape level: header
// presence, request method, body decode, status-code → error mapping.
// No live Supabase call — every test runs against an httptest.Server.

package services

import (
	"context"
	"encoding/json"
	"errors"
	"io"
	"net/http"
	"net/http/httptest"
	"strings"
	"testing"

	"github.com/google/uuid"
)

func TestSupabaseAdminClient_Disabled(t *testing.T) {
	c := NewSupabaseAdminClient("https://example.invalid", "")
	if c.Enabled() {
		t.Fatal("Enabled() must be false when service-role key is empty")
	}
	ctx := context.Background()
	if _, err := c.CreateAuthUser(ctx, "x@hlc.com"); !errors.Is(err, ErrSupabaseAdminUnavailable) {
		t.Errorf("CreateAuthUser must return ErrSupabaseAdminUnavailable, got %v", err)
	}
	if _, err := c.GenerateRecoveryLink(ctx, "x@hlc.com"); !errors.Is(err, ErrSupabaseAdminUnavailable) {
		t.Errorf("GenerateRecoveryLink must return ErrSupabaseAdminUnavailable, got %v", err)
	}
	if err := c.DeleteAuthUser(ctx, uuid.New()); !errors.Is(err, ErrSupabaseAdminUnavailable) {
		t.Errorf("DeleteAuthUser must return ErrSupabaseAdminUnavailable, got %v", err)
	}
}

// TestSupabaseAdminClient_CreateAuthUser_Happy pins the wire-shape:
// POST /auth/v1/admin/users, JSON body with email_confirm=true, both
// apikey + Authorization headers present, parses the response id.
func TestSupabaseAdminClient_CreateAuthUser_Happy(t *testing.T) {
	wantID := uuid.New()
	srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if r.Method != "POST" {
			t.Errorf("method = %q, want POST", r.Method)
		}
		if r.URL.Path != "/auth/v1/admin/users" {
			t.Errorf("path = %q, want /auth/v1/admin/users", r.URL.Path)
		}
		if r.Header.Get("apikey") != "service-key" {
			t.Errorf("missing apikey header")
		}
		if r.Header.Get("Authorization") != "Bearer service-key" {
			t.Errorf("missing Bearer header")
		}
		body, _ := io.ReadAll(r.Body)
		var got map[string]any
		_ = json.Unmarshal(body, &got)
		if got["email"] != "x@hlc.com" {
			t.Errorf("email = %v, want x@hlc.com", got["email"])
		}
		if got["email_confirm"] != true {
			t.Errorf("email_confirm = %v, want true", got["email_confirm"])
		}
		w.Header().Set("Content-Type", "application/json")
		w.WriteHeader(http.StatusOK)
		_ = json.NewEncoder(w).Encode(map[string]string{"id": wantID.String()})
	}))
	defer srv.Close()

	c := NewSupabaseAdminClient(srv.URL, "service-key")
	gotID, err := c.CreateAuthUser(context.Background(), "  X@HLC.COM ")
	if err != nil {
		t.Fatalf("CreateAuthUser: %v", err)
	}
	if gotID != wantID {
		t.Errorf("id = %s, want %s", gotID, wantID)
	}
}

// TestSupabaseAdminClient_CreateAuthUser_EmailExists pins the 422-with-
// "already" body → ErrSupabaseEmailExists translation. Mapped to 409 by
// the handler.
func TestSupabaseAdminClient_CreateAuthUser_EmailExists(t *testing.T) {
	srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		w.WriteHeader(http.StatusUnprocessableEntity)
		_, _ = w.Write([]byte(`{"msg":"A user with this email address has already been registered"}`))
	}))
	defer srv.Close()
	c := NewSupabaseAdminClient(srv.URL, "service-key")
	_, err := c.CreateAuthUser(context.Background(), "dup@hlc.com")
	if !errors.Is(err, ErrSupabaseEmailExists) {
		t.Fatalf("got %v, want ErrSupabaseEmailExists", err)
	}
}

// TestSupabaseAdminClient_GenerateRecoveryLink_BothShapes — Supabase has
// historically returned the link at top-level and nested under
// properties. Both shapes must be accepted.
func TestSupabaseAdminClient_GenerateRecoveryLink_BothShapes(t *testing.T) {
	for _, tc := range []struct {
		name string
		body string
		want string
	}{
		{"top-level", `{"action_link":"https://supabase.paliad.de/auth/v1/verify?token=A"}`, "https://supabase.paliad.de/auth/v1/verify?token=A"},
		{"nested", `{"properties":{"action_link":"https://supabase.paliad.de/auth/v1/verify?token=B"}}`, "https://supabase.paliad.de/auth/v1/verify?token=B"},
	} {
		t.Run(tc.name, func(t *testing.T) {
			srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
				if r.URL.Path != "/auth/v1/admin/generate_link" {
					t.Errorf("path = %q", r.URL.Path)
				}
				body, _ := io.ReadAll(r.Body)
				if !strings.Contains(string(body), `"type":"recovery"`) {
					t.Errorf("body missing type=recovery: %s", body)
				}
				w.Header().Set("Content-Type", "application/json")
				_, _ = w.Write([]byte(tc.body))
			}))
			defer srv.Close()
			c := NewSupabaseAdminClient(srv.URL, "service-key")
			got, err := c.GenerateRecoveryLink(context.Background(), "x@hlc.com")
			if err != nil {
				t.Fatalf("GenerateRecoveryLink: %v", err)
			}
			if got != tc.want {
				t.Errorf("link = %q, want %q", got, tc.want)
			}
		})
	}
}

// TestSupabaseAdminClient_DeleteAuthUser pins the DELETE-by-id route shape
// + 2xx happy path; the cleanup runs after a paliad.users insert failure
// in AdminCreateUserFull, so the round-trip needs to work even with a
// short context window.
func TestSupabaseAdminClient_DeleteAuthUser(t *testing.T) {
	id := uuid.New()
	srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if r.Method != "DELETE" {
			t.Errorf("method = %q", r.Method)
		}
		if r.URL.Path != "/auth/v1/admin/users/"+id.String() {
			t.Errorf("path = %q", r.URL.Path)
		}
		w.WriteHeader(http.StatusOK)
	}))
	defer srv.Close()
	c := NewSupabaseAdminClient(srv.URL, "service-key")
	if err := c.DeleteAuthUser(context.Background(), id); err != nil {
		t.Errorf("DeleteAuthUser: %v", err)
	}
}
