package jwtauth

import (
	"context"
	"strings"
)

// Claims is the authenticated identity for a request. In prod it is sourced from the
// Auth Broker at the edge: broker /verify authenticates the caller and injects X-Auth-*
// headers this process trusts (Caddy strips any client-supplied X-Auth-* at the trust
// boundary, so only the broker can set them). In dev it is synthesised from the dev token.
type Claims struct {
	UserID string   // X-Auth-Subject (Casdoor sub)
	Name   string   // X-Auth-Name (display name); may be empty
	Avatar string   // X-Auth-Avatar (profile picture URL from the broker account); may be empty
	Groups []string // X-Auth-Roles, comma-split
	// Scope is the raw X-Auth-Scope: a global SSO user is "full"; a cdrop delegated
	// session is "app:cdrop:<tier>". Tier() reads the capability grade off the end.
	Scope string
	// DeviceID is X-Auth-Meta: the cdrop device_id this session was minted for — the
	// join key to the devices row. Empty for an unmanaged caller (e.g. a global SSO
	// browser that never paired through cdrop).
	DeviceID string
}

// ScopeTier returns the capability grade — the last colon-separated segment of a broker
// scope ("app:cdrop:guest" → "guest", "full" → "full"). A tierless scope is its own tier.
// Shared by Claims.Tier() and the session-list overlay so the two never diverge.
func ScopeTier(scope string) string {
	if i := strings.LastIndex(scope, ":"); i >= 0 {
		return scope[i+1:]
	}
	return scope
}

// Tier returns the capability grade off the caller's scope (see ScopeTier).
func (c *Claims) Tier() string {
	return ScopeTier(c.Scope)
}

// Guest reports whether these claims came from a restricted guest session (a
// scan-login borrow). Guest sessions can transfer files but not manage devices,
// approve other devices, or mint long-lived tokens.
func (c *Claims) Guest() bool { return c.Tier() == "guest" }

type ctxKey int

const (
	claimsCtxKey ctxKey = iota
	deviceCtxKey
	deviceTypeCtxKey
)

func ClaimsFromContext(ctx context.Context) (*Claims, bool) {
	c, ok := ctx.Value(claimsCtxKey).(*Claims)
	return c, ok
}

// ContextWithClaims attaches claims to a context — the inverse of ClaimsFromContext.
// The auth middleware uses this; it is also the seam handlers and tests use to inject
// claims directly.
func ContextWithClaims(ctx context.Context, c *Claims) context.Context {
	return context.WithValue(ctx, claimsCtxKey, c)
}

func DeviceNameFromContext(ctx context.Context) (string, bool) {
	n, ok := ctx.Value(deviceCtxKey).(string)
	return n, ok
}

// ContextWithDeviceName attaches a device name to the context — the inverse of
// DeviceNameFromContext. Used by tests that exercise handlers directly without
// running the full auth middleware.
func ContextWithDeviceName(ctx context.Context, name string) context.Context {
	return context.WithValue(ctx, deviceCtxKey, name)
}

// DeviceTypeFromContext returns the client-declared device type set by the auth
// middleware (browser / macos / windows / linux / ios), defaulting to "browser".
func DeviceTypeFromContext(ctx context.Context) string {
	t, ok := ctx.Value(deviceTypeCtxKey).(string)
	if !ok || t == "" {
		return "browser"
	}
	return t
}
