package auth

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

	"github.com/go-chi/chi/v5"
)

// Full authorization-chain test (audit §9/§10/§38) WITHOUT a database.
// Composes the REAL Middleware + ScopeMiddleware + RequireApp +
// RequirePermission exactly as cmd/server/main.go does, over a chi router,
// using the existing in-memory fakeStore. Verifies the layered chain:
// Authentication → Tenant Scope → Application Access → Permission → Handler.

// registerToken stores a token for u keyed by "tok-<role>" so the fake
func registerToken(f *fakeStore, u *User) {
	f.tokens[HashToken("tok-"+string(u.Role))] = &Token{ID: 1, TokenableID: u.ID}
	f.byID[u.ID] = u
}

// buildChain mirrors main.go's per-group composition for an ERP-guarded
// resource: auth → scope → RequireApp(AppERP) → RequirePermission(perm).
func buildChain(users ...*User) http.Handler {
	f := newFake()
	for _, u := range users {
		registerToken(f, u)
	}
	svc := NewService(f, time.UTC, 0)
	scope := func(ctx context.Context, userID int64) ([]int64, error) {
		return []int64{1, 2}, nil // tenant membership for scoped users
	}
	r := chi.NewRouter()
	r.Use(Middleware(svc, ScopeMiddleware(scope)))
	r.Handle("/res", chainHandler(
		RequireApp(AppERP),
		RequirePermission("sales.sales-orders.view"),
	))
	return r
}

// chainHandler applies the middlewares around a 200 handler (inner→outer).
func chainHandler(mws ...func(http.Handler) http.Handler) http.Handler {
	h := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(200) }))
	for i := len(mws) - 1; i >= 0; i-- {
		h = mws[i](h)
	}
	return h
}

func doChain(t *testing.T, h http.Handler, token string) int {
	t.Helper()
	req := httptest.NewRequest(http.MethodGet, "/res", nil)
	if token != "" {
		req.Header.Set("Authorization", "Bearer "+token)
	}
	rec := httptest.NewRecorder()
	h.ServeHTTP(rec, req)
	return rec.Code
}

func TestChainAuthScopeAppPermission(t *testing.T) {
	installRoleFixture(t)
	erpSales := &User{ID: 1, Role: RoleSales, ApplicationAccess: AppERP, IsActive: true}
	owner := &User{ID: 2, Role: RoleOwner, ApplicationAccess: AppPlatform, IsActive: true}
	buyer := &User{ID: 3, Role: RoleBuyer, ApplicationAccess: AppECommerce, IsActive: true}
	h := buildChain(erpSales, owner, buyer)

	// ERP sales: app OK + perm OK → 200.
	if c := doChain(t, h, "tok-sales"); c != 200 {
		t.Errorf("erp sales should pass: got %d", c)
	}
	// Owner (platform): app OK + Can() bypass → 200.
	if c := doChain(t, h, "tok-owner"); c != 200 {
		t.Errorf("owner should pass: got %d", c)
	}
	// Buyer: app mismatch (ecommerce≠erp) → 403 at RequireApp, before permission.
	if c := doChain(t, h, "tok-buyer"); c != 403 {
		t.Errorf("buyer must be 403 on erp route: got %d", c)
	}
	// No token → 401.
	if c := doChain(t, h, ""); c != 401 {
		t.Errorf("missing token must be 401: got %d", c)
	}
}

func TestChainPermissionDeniedWithinApp(t *testing.T) {
	installRoleFixture(t)
	// ERP finance has AppERP but lacks sales.sales-orders.view → 403 at perm.
	finance := &User{ID: 4, Role: RoleFinance, ApplicationAccess: AppERP, IsActive: true}
	h := buildChain(finance)
	if c := doChain(t, h, "tok-finance"); c != 403 {
		t.Errorf("erp finance without the perm must be 403: got %d", c)
	}
}

func TestChainScopeInjectedForHandler(t *testing.T) {
	// The ScopeMiddleware must inject tenant context the handler can read —
	// the multi-tenant boundary (audit §8/§32).
	seen := false
	inner := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if sc := StoreScopeFrom(r.Context()); !sc.PlatformWide() && sc.AllowsID(1) {
			seen = true
		}
		w.WriteHeader(200)
	})
	f := newFake(&User{ID: 9, Role: RoleSales, ApplicationAccess: AppERP, IsActive: true})
	registerToken(f, &User{ID: 9, Role: RoleSales, ApplicationAccess: AppERP, IsActive: true})
	svc := NewService(f, time.UTC, 0)
	scoped := Middleware(svc, ScopeMiddleware(func(ctx context.Context, userID int64) ([]int64, error) {
		return []int64{1, 2}, nil
	}))(inner)

	req := httptest.NewRequest(http.MethodGet, "/", nil).WithContext(
		ToContext(context.Background(), &User{ID: 9, Role: RoleSales, ApplicationAccess: AppERP}, 1))
	req.Header.Set("Authorization", "Bearer tok-sales")
	rec := httptest.NewRecorder()
	scoped.ServeHTTP(rec, req)
	if !seen {
		t.Fatal("tenant scope was not injected for the handler")
	}
}

func TestChainEcomAdminRejectedOnErp(t *testing.T) {
	// A B2B ecommerce admin (AppECommerceAdmin) hitting an ERP-guarded route
	// must be 403 — the application axis stops it before any permission check.
	adm := &User{ID: 5, Role: RoleSales, ApplicationAccess: AppECommerceAdmin, IsActive: true}
	h := buildChain(adm)
	if c := doChain(t, h, "tok-sales"); c != 403 {
		t.Errorf("ecommerce admin must be 403 on erp route: got %d", c)
	}
}
