package authd

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

	"github.com/golang-jwt/jwt/v5"
)

func newTestVerifier(t *testing.T) *Authd {
	t.Helper()
	cfg := DefaultConfig()
	cfg.JWTSecret = []byte("test-secret-with-32-bytes-min-len-abc")
	cfg.BcryptCost = 4
	a, err := New(nil, cfg)
	if err != nil {
		t.Fatalf("new: %v", err)
	}
	return a
}

func mintToken(t *testing.T, a *Authd, user *User) string {
	t.Helper()
	tok, _, _, err := a.mintAccessToken(user)
	if err != nil {
		t.Fatalf("mint: %v", err)
	}
	return tok
}

func TestRequireAuth_NoHeader(t *testing.T) {
	a := newTestVerifier(t)
	called := false
	h := a.RequireAuth(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		called = true
	}))
	rr := httptest.NewRecorder()
	h.ServeHTTP(rr, httptest.NewRequest("GET", "/", nil))
	if rr.Code != http.StatusUnauthorized {
		t.Errorf("status = %d, want 401", rr.Code)
	}
	if called {
		t.Error("downstream handler was called despite missing header")
	}
	if !strings.Contains(rr.Body.String(), "missing_bearer") {
		t.Errorf("body = %q, want it to contain 'missing_bearer'", rr.Body.String())
	}
}

func TestRequireAuth_BadScheme(t *testing.T) {
	a := newTestVerifier(t)
	called := false
	h := a.RequireAuth(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		called = true
	}))
	rr := httptest.NewRecorder()
	req := httptest.NewRequest("GET", "/", nil)
	req.Header.Set("Authorization", "Basic dXNlcjpwYXNz")
	h.ServeHTTP(rr, req)
	if rr.Code != http.StatusUnauthorized {
		t.Errorf("status = %d, want 401", rr.Code)
	}
	if called {
		t.Error("handler called with non-Bearer scheme")
	}
}

func TestRequireAuth_BadToken(t *testing.T) {
	a := newTestVerifier(t)
	called := false
	h := a.RequireAuth(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		called = true
	}))
	rr := httptest.NewRecorder()
	req := httptest.NewRequest("GET", "/", nil)
	req.Header.Set("Authorization", "Bearer not-a-jwt")
	h.ServeHTTP(rr, req)
	if rr.Code != http.StatusUnauthorized {
		t.Errorf("status = %d, want 401", rr.Code)
	}
	if called {
		t.Error("handler called with bad token")
	}
}

func TestRequireAuth_Expired(t *testing.T) {
	cfg := DefaultConfig()
	cfg.JWTSecret = []byte("test-secret-with-32-bytes-min-len-abc")
	cfg.AccessTokenTTL = -1 * time.Minute
	a, _ := New(nil, cfg)
	tok, _, _, _ := a.mintAccessToken(&User{ID: "u-1", Status: "active"})

	called := false
	h := a.RequireAuth(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		called = true
	}))
	rr := httptest.NewRecorder()
	req := httptest.NewRequest("GET", "/", nil)
	req.Header.Set("Authorization", "Bearer "+tok)
	h.ServeHTTP(rr, req)
	if rr.Code != http.StatusUnauthorized {
		t.Errorf("expired token: status = %d, want 401", rr.Code)
	}
	if called {
		t.Error("handler called with expired token")
	}
}

func TestRequireAuth_Valid(t *testing.T) {
	a := newTestVerifier(t)
	tok := mintToken(t, a, &User{ID: "u-1", TenantID: "t-1", Role: "tenant_admin", Status: "active"})

	var gotClaims *AccessClaims
	called := false
	h := a.RequireAuth(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		called = true
		gotClaims = ClaimsFromContext(r.Context())
		w.WriteHeader(http.StatusOK)
	}))
	rr := httptest.NewRecorder()
	req := httptest.NewRequest("GET", "/", nil)
	req.Header.Set("Authorization", "Bearer "+tok)
	h.ServeHTTP(rr, req)
	if rr.Code != http.StatusOK {
		t.Errorf("status = %d, want 200", rr.Code)
	}
	if !called {
		t.Error("handler not called with valid token")
	}
	if gotClaims == nil {
		t.Fatal("claims not in context")
	}
	if gotClaims.UserID != "u-1" {
		t.Errorf("UserID = %q, want u-1", gotClaims.UserID)
	}
	if gotClaims.TenantID != "t-1" {
		t.Errorf("TenantID = %q, want t-1", gotClaims.TenantID)
	}
	if gotClaims.Role != "tenant_admin" {
		t.Errorf("Role = %q, want tenant_admin", gotClaims.Role)
	}
}

func TestRequireAuth_AlgNone(t *testing.T) {
	// Forge a token signed with alg=none, claim super_admin. The
	// middleware must reject it.
	a := newTestVerifier(t)
	claims := AccessClaims{
		UserID:    "u-evil",
		Role:      "super_admin",
		TokenType: "access",
		RegisteredClaims: jwt.RegisteredClaims{
			Issuer:    "broad-announce",
			Subject:   "u-evil",
			ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Hour)),
		},
	}
	t1 := jwt.NewWithClaims(jwt.SigningMethodNone, claims)
	noneToken, err := t1.SignedString(jwt.UnsafeAllowNoneSignatureType)
	if err != nil {
		t.Fatalf("sign none: %v", err)
	}

	called := false
	h := a.RequireAuth(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		called = true
	}))
	rr := httptest.NewRecorder()
	req := httptest.NewRequest("GET", "/", nil)
	req.Header.Set("Authorization", "Bearer "+noneToken)
	h.ServeHTTP(rr, req)
	if rr.Code != http.StatusUnauthorized {
		t.Errorf("alg=none: status = %d, want 401", rr.Code)
	}
	if called {
		t.Error("handler called with alg=none token (CRITICAL)")
	}
}

func TestRequireRole_Allowed(t *testing.T) {
	a := newTestVerifier(t)
	tok := mintToken(t, a, &User{ID: "u-1", Role: "super_admin", Status: "active"})

	called := false
	h := a.RequireRole("super_admin", "tenant_admin")(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		called = true
		w.WriteHeader(http.StatusOK)
	}))
	rr := httptest.NewRecorder()
	req := httptest.NewRequest("GET", "/", nil)
	req.Header.Set("Authorization", "Bearer "+tok)
	h.ServeHTTP(rr, req)
	if rr.Code != http.StatusOK {
		t.Errorf("status = %d, want 200", rr.Code)
	}
	if !called {
		t.Error("handler not called for allowed role")
	}
}

func TestRequireRole_Forbidden(t *testing.T) {
	a := newTestVerifier(t)
	tok := mintToken(t, a, &User{ID: "u-1", Role: "viewer", Status: "active"})

	called := false
	h := a.RequireRole("super_admin")(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		called = true
	}))
	rr := httptest.NewRecorder()
	req := httptest.NewRequest("GET", "/", nil)
	req.Header.Set("Authorization", "Bearer "+tok)
	h.ServeHTTP(rr, req)
	if rr.Code != http.StatusForbidden {
		t.Errorf("status = %d, want 403", rr.Code)
	}
	if called {
		t.Error("handler called for forbidden role")
	}
}

func TestRequireRole_NoToken(t *testing.T) {
	a := newTestVerifier(t)
	called := false
	h := a.RequireRole("super_admin")(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		called = true
	}))
	rr := httptest.NewRecorder()
	h.ServeHTTP(rr, httptest.NewRequest("GET", "/", nil))
	if rr.Code != http.StatusUnauthorized {
		t.Errorf("status = %d, want 401 (no token wins over forbidden)", rr.Code)
	}
	if called {
		t.Error("handler called with no token")
	}
}

func TestClaimsFromContext_Empty(t *testing.T) {
	if c := ClaimsFromContext(context.Background()); c != nil {
		t.Errorf("expected nil for empty ctx, got %+v", c)
	}
}

func TestBearerFromRequest_CaseInsensitive(t *testing.T) {
	tests := []struct {
		header  string
		want    string
		wantErr bool
	}{
		{"Bearer abc", "abc", false},
		{"bearer abc", "abc", false},
		{"BEARER abc", "abc", false},
		{"Bearer  abc", "abc", false}, // double space tolerated
		{"Bearer\tabc", "abc", false}, // tab tolerated
		{"Basic abc", "", true},
		{"", "", true},
		{"Bearer", "", true},
		{"Bearer ", "", true},
	}
	for _, tt := range tests {
		t.Run(tt.header, func(t *testing.T) {
			r := httptest.NewRequest("GET", "/", nil)
			if tt.header != "" {
				r.Header.Set("Authorization", tt.header)
			}
			got, err := bearerFromRequest(r)
			if (err != nil) != tt.wantErr {
				t.Errorf("err = %v, wantErr %v", err, tt.wantErr)
			}
			if got != tt.want {
				t.Errorf("got = %q, want %q", got, tt.want)
			}
		})
	}
}
