package auth

import (
	"context"
	"database/sql"
	"encoding/json"
	"errors"
	"fmt"
	"path"
	"strings"
	"time"

	"github.com/go-webauthn/webauthn/webauthn"
)

type Repository struct {
	db *sql.DB
}

func NewRepository(db *sql.DB) *Repository {
	return &Repository{db: db}
}

func (r *Repository) UpsertUser(ctx context.Context, user User) (User, error) {
	now := time.Now().UTC()
	if user.ID == "" {
		user.ID = "user:" + user.Email
	}
	if user.Role == "" {
		user.Role = RoleViewer
	}
	if user.DisplayName == "" {
		user.DisplayName = user.Email
	}

	if _, err := r.db.ExecContext(ctx, `
		INSERT INTO users (id, email, display_name, password_hash, role, created_at, last_seen_at)
		VALUES (?, ?, ?, ?, ?, ?, ?)
		ON CONFLICT(email) DO UPDATE SET
			display_name = excluded.display_name,
			password_hash = COALESCE(excluded.password_hash, users.password_hash),
			role = excluded.role,
			last_seen_at = excluded.last_seen_at
	`, user.ID, user.Email, user.DisplayName, nullString(user.PasswordHash), string(user.Role), now.Format(time.RFC3339), now.Format(time.RFC3339)); err != nil {
		return User{}, fmt.Errorf("upsert user: %w", err)
	}

	return r.GetUserByEmail(ctx, user.Email)
}

func (r *Repository) GetUserByEmail(ctx context.Context, email string) (User, error) {
	var user User
	var created, lastSeen, passwordHash, role string
	var disabled int
	err := r.db.QueryRowContext(ctx, `
		SELECT id, email, COALESCE(display_name, ''), COALESCE(password_hash, ''), COALESCE(role, 'viewer'), disabled, created_at, COALESCE(last_seen_at, '')
		FROM users
		WHERE email = ?
	`, email).Scan(&user.ID, &user.Email, &user.DisplayName, &passwordHash, &role, &disabled, &created, &lastSeen)
	if err != nil {
		return User{}, err
	}
	user.PasswordHash = passwordHash
	user.Role = Role(role)
	user.Disabled = disabled == 1
	user.CreatedAt, err = time.Parse(time.RFC3339, created)
	if err != nil {
		return User{}, fmt.Errorf("parse user created_at: %w", err)
	}
	if lastSeen != "" {
		user.LastSeenAt, err = time.Parse(time.RFC3339, lastSeen)
		if err != nil {
			return User{}, fmt.Errorf("parse user last_seen_at: %w", err)
		}
	}
	user.Credentials, err = r.ListCredentials(ctx, user.ID)
	if err != nil {
		return User{}, err
	}
	return user, nil
}

func (r *Repository) GetUserByID(ctx context.Context, userID string) (User, error) {
	var email string
	if err := r.db.QueryRowContext(ctx, `SELECT email FROM users WHERE id = ?`, userID).Scan(&email); err != nil {
		return User{}, err
	}
	return r.GetUserByEmail(ctx, email)
}

func (r *Repository) CountUsers(ctx context.Context) (int, error) {
	var count int
	if err := r.db.QueryRowContext(ctx, `SELECT COUNT(*) FROM users`).Scan(&count); err != nil {
		return 0, err
	}
	return count, nil
}

func (r *Repository) CountAdmins(ctx context.Context) (int, error) {
	var count int
	if err := r.db.QueryRowContext(ctx, `SELECT COUNT(*) FROM users WHERE role = 'admin' AND disabled = 0`).Scan(&count); err != nil {
		return 0, err
	}
	return count, nil
}

func (r *Repository) GetInstanceSettings(ctx context.Context) (InstanceSettings, error) {
	var setupCompleted string
	var signupsEnabled int
	if err := r.db.QueryRowContext(ctx, `
		SELECT COALESCE(setup_completed_at, ''), signups_enabled
		FROM instance_settings
		WHERE id = 1
	`).Scan(&setupCompleted, &signupsEnabled); err != nil {
		return InstanceSettings{}, fmt.Errorf("get instance settings: %w", err)
	}
	return InstanceSettings{
		SetupComplete:  setupCompleted != "",
		SignupsEnabled: signupsEnabled == 1,
	}, nil
}

func (r *Repository) CompleteInitialSetup(ctx context.Context, user User, signupsEnabled bool) (User, error) {
	now := time.Now().UTC()
	if user.ID == "" {
		user.ID = "user:" + user.Email
	}
	if user.DisplayName == "" {
		user.DisplayName = user.Email
	}

	tx, err := r.db.BeginTx(ctx, nil)
	if err != nil {
		return User{}, fmt.Errorf("begin initial setup: %w", err)
	}
	defer func() { _ = tx.Rollback() }()

	var setupCompleted string
	if err := tx.QueryRowContext(ctx, `
		SELECT COALESCE(setup_completed_at, '')
		FROM instance_settings
		WHERE id = 1
	`).Scan(&setupCompleted); err != nil {
		return User{}, fmt.Errorf("get initial setup state: %w", err)
	}
	if setupCompleted != "" {
		return User{}, fmt.Errorf("initial setup has already been completed")
	}

	var userCount int
	if err := tx.QueryRowContext(ctx, `SELECT COUNT(*) FROM users`).Scan(&userCount); err != nil {
		return User{}, fmt.Errorf("count users during initial setup: %w", err)
	}
	if userCount != 0 {
		return User{}, fmt.Errorf("initial setup requires an empty user database")
	}

	if _, err := tx.ExecContext(ctx, `
		INSERT INTO users (id, email, display_name, password_hash, role, created_at, last_seen_at)
		VALUES (?, ?, ?, ?, ?, ?, ?)
	`, user.ID, user.Email, user.DisplayName, nullString(user.PasswordHash), string(RoleAdmin), now.Format(time.RFC3339), now.Format(time.RFC3339)); err != nil {
		return User{}, fmt.Errorf("create initial admin: %w", err)
	}

	if _, err := tx.ExecContext(ctx, `
		UPDATE instance_settings
		SET setup_completed_at = ?, signups_enabled = ?, updated_at = ?
		WHERE id = 1 AND setup_completed_at IS NULL
	`, now.Format(time.RFC3339), boolInt(signupsEnabled), now.Format(time.RFC3339)); err != nil {
		return User{}, fmt.Errorf("complete initial setup: %w", err)
	}

	if err := tx.Commit(); err != nil {
		return User{}, fmt.Errorf("commit initial setup: %w", err)
	}
	return r.GetUserByEmail(ctx, user.Email)
}

func (r *Repository) UpdateSignupsEnabled(ctx context.Context, enabled bool) error {
	result, err := r.db.ExecContext(ctx, `
		UPDATE instance_settings
		SET signups_enabled = ?, updated_at = ?
		WHERE id = 1 AND setup_completed_at IS NOT NULL
	`, boolInt(enabled), time.Now().UTC().Format(time.RFC3339))
	if err != nil {
		return fmt.Errorf("update signup setting: %w", err)
	}
	if rows, _ := result.RowsAffected(); rows == 0 {
		return fmt.Errorf("initial setup is required")
	}
	return nil
}

func (r *Repository) ListUsers(ctx context.Context) ([]User, error) {
	rows, err := r.db.QueryContext(ctx, `
		SELECT id, email, COALESCE(display_name, ''), COALESCE(password_hash, ''), COALESCE(role, 'viewer'), disabled, created_at, COALESCE(last_seen_at, '')
		FROM users
		ORDER BY created_at DESC
	`)
	if err != nil {
		return nil, fmt.Errorf("list users: %w", err)
	}
	defer rows.Close()

	var users []User
	for rows.Next() {
		var user User
		var created, lastSeen, role string
		var disabled int
		if err := rows.Scan(&user.ID, &user.Email, &user.DisplayName, &user.PasswordHash, &role, &disabled, &created, &lastSeen); err != nil {
			return nil, fmt.Errorf("scan user: %w", err)
		}
		user.Role = Role(role)
		user.Disabled = disabled == 1
		user.CreatedAt, _ = time.Parse(time.RFC3339, created)
		if lastSeen != "" {
			user.LastSeenAt, _ = time.Parse(time.RFC3339, lastSeen)
		}
		users = append(users, user)
	}
	return users, rows.Err()
}

func (r *Repository) UpdatePasswordHash(ctx context.Context, userID, passwordHash string) error {
	result, err := r.db.ExecContext(ctx, `
		UPDATE users
		SET password_hash = ?, last_seen_at = ?
		WHERE id = ?
	`, passwordHash, time.Now().UTC().Format(time.RFC3339), userID)
	if err != nil {
		return err
	}
	if rows, _ := result.RowsAffected(); rows == 0 {
		return sql.ErrNoRows
	}
	return nil
}

func (r *Repository) UpdateUserRole(ctx context.Context, userID string, role Role) error {
	result, err := r.db.ExecContext(ctx, `
		UPDATE users
		SET role = ?, last_seen_at = ?
		WHERE id = ?
	`, string(role), time.Now().UTC().Format(time.RFC3339), userID)
	if err != nil {
		return err
	}
	if rows, _ := result.RowsAffected(); rows == 0 {
		return sql.ErrNoRows
	}
	return nil
}

func (r *Repository) UpdateUserAccess(ctx context.Context, updates []UserAccessUpdate) error {
	tx, err := r.db.BeginTx(ctx, nil)
	if err != nil {
		return fmt.Errorf("begin user access update: %w", err)
	}
	defer func() { _ = tx.Rollback() }()

	for _, update := range updates {
		result, err := tx.ExecContext(ctx, `
			UPDATE users
			SET role = ?, disabled = ?
			WHERE id = ?
		`, string(update.Role), boolInt(update.Disabled), update.ID)
		if err != nil {
			return fmt.Errorf("update user access: %w", err)
		}
		if rows, _ := result.RowsAffected(); rows == 0 {
			return sql.ErrNoRows
		}
	}

	var admins int
	if err := tx.QueryRowContext(ctx, `SELECT COUNT(*) FROM users WHERE role = 'admin' AND disabled = 0`).Scan(&admins); err != nil {
		return fmt.Errorf("count enabled admins: %w", err)
	}
	if admins == 0 {
		return fmt.Errorf("cannot disable or demote the last admin account")
	}

	if err := tx.Commit(); err != nil {
		return fmt.Errorf("commit user access update: %w", err)
	}
	return nil
}

type resourceMatch struct {
	resourceType ResourceType
	resourceID   string
}

func (r *Repository) ListResourcePermissions(ctx context.Context, resourceType ResourceType, resourceID string) ([]ResourcePermission, error) {
	resourceType, resourceID, err := normalizeResource(resourceType, resourceID)
	if err != nil {
		return nil, err
	}
	rows, err := r.db.QueryContext(ctx, `
		SELECT p.id, p.user_id, COALESCE(u.email, ''), COALESCE(u.display_name, ''), p.resource_type, COALESCE(p.resource_id, ''), p.permission, COALESCE(p.granted_by, ''), p.created_at
		FROM permissions p
		LEFT JOIN users u ON u.id = p.user_id
		WHERE p.resource_type = ? AND COALESCE(p.resource_id, '') = ?
		ORDER BY u.email ASC, p.created_at ASC
	`, string(resourceType), resourceID)
	if err != nil {
		return nil, fmt.Errorf("list resource permissions: %w", err)
	}
	defer rows.Close()

	var permissions []ResourcePermission
	for rows.Next() {
		permission, err := scanResourcePermission(rows)
		if err != nil {
			return nil, err
		}
		permissions = append(permissions, permission)
	}
	return permissions, rows.Err()
}

func (r *Repository) ReplaceResourcePermissions(ctx context.Context, resourceType ResourceType, resourceID string, actorID string, updates []ResourcePermissionUpdate) error {
	resourceType, resourceID, err := normalizeResource(resourceType, resourceID)
	if err != nil {
		return err
	}
	tx, err := r.db.BeginTx(ctx, nil)
	if err != nil {
		return fmt.Errorf("begin resource permissions update: %w", err)
	}
	defer func() { _ = tx.Rollback() }()

	if _, err := tx.ExecContext(ctx, `
		DELETE FROM permissions
		WHERE resource_type = ? AND COALESCE(resource_id, '') = ?
	`, string(resourceType), resourceID); err != nil {
		return fmt.Errorf("clear resource permissions: %w", err)
	}

	now := time.Now().UTC().Format(time.RFC3339)
	for _, update := range updates {
		update.UserID = strings.TrimSpace(update.UserID)
		if update.UserID == "" || update.Permission == PermissionNone {
			continue
		}
		if !validPermission(update.Permission) {
			return fmt.Errorf("invalid permission")
		}
		var exists int
		if err := tx.QueryRowContext(ctx, `SELECT COUNT(*) FROM users WHERE id = ? AND disabled = 0`, update.UserID).Scan(&exists); err != nil {
			return fmt.Errorf("check permission user: %w", err)
		}
		if exists == 0 {
			return fmt.Errorf("user %s does not exist", update.UserID)
		}
		idPart, err := randomToken(18)
		if err != nil {
			return err
		}
		if _, err := tx.ExecContext(ctx, `
			INSERT INTO permissions (id, user_id, resource_type, resource_id, permission, granted_by, created_at)
			VALUES (?, ?, ?, ?, ?, ?, ?)
		`, "perm:"+idPart, update.UserID, string(resourceType), nullString(resourceID), string(update.Permission), nullString(actorID), now); err != nil {
			return fmt.Errorf("insert resource permission: %w", err)
		}
	}

	if err := tx.Commit(); err != nil {
		return fmt.Errorf("commit resource permissions update: %w", err)
	}
	return nil
}

func (r *Repository) EnsureResourcePermission(ctx context.Context, resourceType ResourceType, resourceID string, actorID string, update ResourcePermissionUpdate) error {
	resourceType, resourceID, err := normalizeResource(resourceType, resourceID)
	if err != nil {
		return err
	}
	update.UserID = strings.TrimSpace(update.UserID)
	if update.UserID == "" {
		return fmt.Errorf("user id is required")
	}
	if !validPermission(update.Permission) || update.Permission == PermissionNone {
		return fmt.Errorf("invalid permission")
	}

	tx, err := r.db.BeginTx(ctx, nil)
	if err != nil {
		return fmt.Errorf("begin resource permission ensure: %w", err)
	}
	defer func() { _ = tx.Rollback() }()

	var exists int
	if err := tx.QueryRowContext(ctx, `SELECT COUNT(*) FROM users WHERE id = ? AND disabled = 0`, update.UserID).Scan(&exists); err != nil {
		return fmt.Errorf("check permission user: %w", err)
	}
	if exists == 0 {
		return fmt.Errorf("user %s does not exist", update.UserID)
	}

	best := PermissionNone
	rows, err := tx.QueryContext(ctx, `
		SELECT permission
		FROM permissions
		WHERE user_id = ? AND resource_type = ? AND COALESCE(resource_id, '') = ?
	`, update.UserID, string(resourceType), resourceID)
	if err != nil {
		return fmt.Errorf("lookup existing resource permission: %w", err)
	}
	for rows.Next() {
		var raw string
		if err := rows.Scan(&raw); err != nil {
			rows.Close()
			return fmt.Errorf("scan existing resource permission: %w", err)
		}
		best = maxPermission(best, Permission(raw))
	}
	if err := rows.Close(); err != nil {
		return err
	}
	if PermissionAllows(best, update.Permission) {
		return tx.Commit()
	}

	if _, err := tx.ExecContext(ctx, `
		DELETE FROM permissions
		WHERE user_id = ? AND resource_type = ? AND COALESCE(resource_id, '') = ?
	`, update.UserID, string(resourceType), resourceID); err != nil {
		return fmt.Errorf("clear user resource permission: %w", err)
	}

	idPart, err := randomToken(18)
	if err != nil {
		return err
	}
	if _, err := tx.ExecContext(ctx, `
		INSERT INTO permissions (id, user_id, resource_type, resource_id, permission, granted_by, created_at)
		VALUES (?, ?, ?, ?, ?, ?, ?)
	`, "perm:"+idPart, update.UserID, string(resourceType), nullString(resourceID), string(update.Permission), nullString(actorID), time.Now().UTC().Format(time.RFC3339)); err != nil {
		return fmt.Errorf("insert resource permission: %w", err)
	}

	if err := tx.Commit(); err != nil {
		return fmt.Errorf("commit resource permission ensure: %w", err)
	}
	return nil
}

func (r *Repository) EffectiveResourcePermission(ctx context.Context, principal Principal, resourceType ResourceType, resourceID string, collectionIDs []string) (Permission, error) {
	if principal.UserID == "" {
		return PermissionNone, nil
	}
	if principal.Role == RoleAdmin {
		return PermissionAdmin, nil
	}
	resourceType, resourceID, err := normalizeResource(resourceType, resourceID)
	if err != nil {
		return PermissionNone, err
	}

	matches := []resourceMatch{{resourceType: ResourceGlobal, resourceID: ""}}
	switch resourceType {
	case ResourceDocument:
		for _, folder := range folderMatchesForDocument(resourceID) {
			matches = append(matches, resourceMatch{resourceType: ResourceFolder, resourceID: folder})
		}
		for _, collectionID := range collectionIDs {
			normalized := normalizeCollectionID(collectionID)
			if normalized != "" {
				matches = append(matches, resourceMatch{resourceType: ResourceCollection, resourceID: normalized})
			}
		}
		matches = append(matches, resourceMatch{resourceType: ResourceDocument, resourceID: resourceID})
	case ResourceFolder:
		for _, folder := range folderMatches(resourceID) {
			matches = append(matches, resourceMatch{resourceType: ResourceFolder, resourceID: folder})
		}
	case ResourceCollection:
		matches = append(matches, resourceMatch{resourceType: ResourceCollection, resourceID: resourceID})
	default:
		matches = append(matches, resourceMatch{resourceType: resourceType, resourceID: resourceID})
	}

	best := PermissionNone
	for _, match := range matches {
		rows, err := r.db.QueryContext(ctx, `
			SELECT permission
			FROM permissions
			WHERE user_id = ? AND resource_type = ? AND COALESCE(resource_id, '') = ?
		`, principal.UserID, string(match.resourceType), match.resourceID)
		if err != nil {
			return PermissionNone, fmt.Errorf("lookup resource permission: %w", err)
		}
		for rows.Next() {
			var raw string
			if err := rows.Scan(&raw); err != nil {
				rows.Close()
				return PermissionNone, fmt.Errorf("scan resource permission: %w", err)
			}
			best = maxPermission(best, Permission(raw))
		}
		if err := rows.Close(); err != nil {
			return PermissionNone, err
		}
	}
	return best, nil
}

func scanResourcePermission(rows *sql.Rows) (ResourcePermission, error) {
	var (
		permission    ResourcePermission
		resourceType  string
		resourceID    string
		rawPermission string
		created       string
	)
	if err := rows.Scan(&permission.ID, &permission.UserID, &permission.UserEmail, &permission.UserName, &resourceType, &resourceID, &rawPermission, &permission.GrantedBy, &created); err != nil {
		return ResourcePermission{}, fmt.Errorf("scan resource permission: %w", err)
	}
	permission.ResourceType = ResourceType(resourceType)
	permission.ResourceID = resourceID
	permission.Permission = Permission(rawPermission)
	createdAt, err := time.Parse(time.RFC3339, created)
	if err != nil {
		return ResourcePermission{}, fmt.Errorf("parse resource permission created_at: %w", err)
	}
	permission.CreatedAt = createdAt
	return permission, nil
}

func normalizeResource(resourceType ResourceType, resourceID string) (ResourceType, string, error) {
	resourceType = ResourceType(strings.TrimSpace(strings.ToLower(string(resourceType))))
	switch resourceType {
	case ResourceGlobal:
		return resourceType, "", nil
	case ResourceDocument:
		resourceID = normalizeDocumentPath(resourceID)
		if resourceID == "" {
			return "", "", fmt.Errorf("document resource id is required")
		}
		return resourceType, resourceID, nil
	case ResourceFolder:
		return resourceType, normalizeFolderPath(resourceID), nil
	case ResourceCollection:
		resourceID = normalizeCollectionID(resourceID)
		if resourceID == "" {
			return "", "", fmt.Errorf("collection resource id is required")
		}
		return resourceType, resourceID, nil
	default:
		return "", "", fmt.Errorf("invalid resource type")
	}
}

func normalizeDocumentPath(raw string) string {
	clean := normalizeFolderPath(raw)
	if clean == "" {
		return ""
	}
	if !strings.HasSuffix(strings.ToLower(clean), ".md") {
		clean += ".md"
	}
	return clean
}

func normalizeFolderPath(raw string) string {
	raw = strings.TrimSpace(strings.Trim(raw, "/"))
	if raw == "" {
		return ""
	}
	clean := path.Clean(strings.ReplaceAll(raw, "\\", "/"))
	if clean == "." || clean == ".." || strings.HasPrefix(clean, "../") {
		return ""
	}
	return strings.Trim(clean, "/")
}

func normalizeCollectionID(raw string) string {
	return strings.Trim(strings.TrimSpace(raw), "#/")
}

func folderMatchesForDocument(documentPath string) []string {
	folder := path.Dir(documentPath)
	if folder == "." {
		return []string{""}
	}
	return folderMatches(folder)
}

func folderMatches(folder string) []string {
	folder = normalizeFolderPath(folder)
	matches := []string{""}
	if folder == "" {
		return matches
	}
	parts := strings.Split(folder, "/")
	for index := range parts {
		matches = append(matches, strings.Join(parts[:index+1], "/"))
	}
	return matches
}

func validPermission(permission Permission) bool {
	switch permission {
	case PermissionNone, PermissionRead, PermissionWrite, PermissionAdmin:
		return true
	default:
		return false
	}
}

func maxPermission(left Permission, right Permission) Permission {
	if permissionRank(right) > permissionRank(left) {
		return right
	}
	return left
}

func permissionRank(permission Permission) int {
	switch permission {
	case PermissionRead:
		return 1
	case PermissionWrite:
		return 2
	case PermissionAdmin:
		return 3
	default:
		return 0
	}
}

func (r *Repository) DeleteUser(ctx context.Context, userID string) error {
	result, err := r.db.ExecContext(ctx, `DELETE FROM users WHERE id = ?`, userID)
	if err != nil {
		return err
	}
	if rows, _ := result.RowsAffected(); rows == 0 {
		return sql.ErrNoRows
	}
	return nil
}

func (r *Repository) SaveCredential(ctx context.Context, userID string, credential webauthn.Credential) error {
	now := time.Now().UTC().Format(time.RFC3339)
	payload, err := json.Marshal(credential)
	if err != nil {
		return fmt.Errorf("marshal credential: %w", err)
	}
	if _, err := r.db.ExecContext(ctx, `
		INSERT INTO webauthn_credentials (id, user_id, credential_id, credential_json, created_at, last_used_at)
		VALUES (?, ?, ?, ?, ?, ?)
		ON CONFLICT(credential_id) DO UPDATE SET
			credential_json = excluded.credential_json,
			last_used_at = excluded.last_used_at
	`, "cred:"+hashSecret(string(credential.ID))[:24], userID, credential.ID, string(payload), now, now); err != nil {
		return fmt.Errorf("save credential: %w", err)
	}
	return nil
}

func (r *Repository) ListCredentials(ctx context.Context, userID string) ([]webauthn.Credential, error) {
	rows, err := r.db.QueryContext(ctx, `
		SELECT credential_json
		FROM webauthn_credentials
		WHERE user_id = ?
		ORDER BY created_at ASC
	`, userID)
	if err != nil {
		return nil, fmt.Errorf("list credentials: %w", err)
	}
	defer rows.Close()

	var credentials []webauthn.Credential
	for rows.Next() {
		var raw string
		if err := rows.Scan(&raw); err != nil {
			return nil, fmt.Errorf("scan credential: %w", err)
		}
		var credential webauthn.Credential
		if err := json.Unmarshal([]byte(raw), &credential); err != nil {
			return nil, fmt.Errorf("unmarshal credential: %w", err)
		}
		credentials = append(credentials, credential)
	}
	return credentials, rows.Err()
}

func (r *Repository) SaveChallenge(ctx context.Context, userID, challengeType string, session webauthn.SessionData, ttl time.Duration) (string, error) {
	idPart, err := randomToken(18)
	if err != nil {
		return "", err
	}
	payload, err := json.Marshal(session)
	if err != nil {
		return "", fmt.Errorf("marshal challenge: %w", err)
	}
	now := time.Now().UTC()
	id := "chal:" + idPart
	if _, err := r.db.ExecContext(ctx, `
		INSERT INTO auth_challenges (id, user_id, type, session_json, created_at, expires_at)
		VALUES (?, ?, ?, ?, ?, ?)
	`, id, userID, challengeType, string(payload), now.Format(time.RFC3339), now.Add(ttl).Format(time.RFC3339)); err != nil {
		return "", fmt.Errorf("save challenge: %w", err)
	}
	return id, nil
}

func (r *Repository) ConsumeChallenge(ctx context.Context, id, challengeType string) (webauthn.SessionData, string, error) {
	var raw, userID, expires, used string
	err := r.db.QueryRowContext(ctx, `
		SELECT session_json, user_id, expires_at, COALESCE(used_at, '')
		FROM auth_challenges
		WHERE id = ? AND type = ?
	`, id, challengeType).Scan(&raw, &userID, &expires, &used)
	if err != nil {
		return webauthn.SessionData{}, "", err
	}
	if used != "" {
		return webauthn.SessionData{}, "", fmt.Errorf("challenge already used")
	}
	expiresAt, err := time.Parse(time.RFC3339, expires)
	if err != nil {
		return webauthn.SessionData{}, "", fmt.Errorf("parse challenge expiry: %w", err)
	}
	if time.Now().UTC().After(expiresAt) {
		return webauthn.SessionData{}, "", fmt.Errorf("challenge expired")
	}
	var session webauthn.SessionData
	if err := json.Unmarshal([]byte(raw), &session); err != nil {
		return webauthn.SessionData{}, "", fmt.Errorf("unmarshal challenge: %w", err)
	}
	if _, err := r.db.ExecContext(ctx, `UPDATE auth_challenges SET used_at = ? WHERE id = ?`, time.Now().UTC().Format(time.RFC3339), id); err != nil {
		return webauthn.SessionData{}, "", fmt.Errorf("consume challenge: %w", err)
	}
	return session, userID, nil
}

func (r *Repository) CreateSession(ctx context.Context, userID, tokenHash, ip, userAgent string, ttl time.Duration) (Session, error) {
	idPart, err := randomToken(18)
	if err != nil {
		return Session{}, err
	}
	now := time.Now().UTC()
	session := Session{
		ID:        "sess:" + idPart,
		UserID:    userID,
		TokenHash: tokenHash,
		CreatedAt: now,
		ExpiresAt: now.Add(ttl),
	}
	if _, err := r.db.ExecContext(ctx, `
		INSERT INTO sessions (id, user_id, token_hash, created_at, expires_at, last_seen_at, ip_address, user_agent)
		VALUES (?, ?, ?, ?, ?, ?, ?, ?)
	`, session.ID, session.UserID, session.TokenHash, session.CreatedAt.Format(time.RFC3339), session.ExpiresAt.Format(time.RFC3339), now.Format(time.RFC3339), ip, userAgent); err != nil {
		return Session{}, fmt.Errorf("create session: %w", err)
	}
	return session, nil
}

func (r *Repository) ValidateSession(ctx context.Context, tokenHash string) (Session, User, error) {
	var session Session
	var created, expires, revoked string
	err := r.db.QueryRowContext(ctx, `
		SELECT id, user_id, token_hash, created_at, expires_at, COALESCE(revoked_at, '')
		FROM sessions
		WHERE token_hash = ?
	`, tokenHash).Scan(&session.ID, &session.UserID, &session.TokenHash, &created, &expires, &revoked)
	if err != nil {
		return Session{}, User{}, err
	}
	session.CreatedAt, err = time.Parse(time.RFC3339, created)
	if err != nil {
		return Session{}, User{}, err
	}
	session.ExpiresAt, err = time.Parse(time.RFC3339, expires)
	if err != nil {
		return Session{}, User{}, err
	}
	if revoked != "" {
		return Session{}, User{}, fmt.Errorf("session revoked")
	}
	if time.Now().UTC().After(session.ExpiresAt) {
		return Session{}, User{}, fmt.Errorf("session expired")
	}
	user, err := r.GetUserByID(ctx, session.UserID)
	if err != nil {
		return Session{}, User{}, err
	}
	_, _ = r.db.ExecContext(ctx, `UPDATE sessions SET last_seen_at = ? WHERE id = ?`, time.Now().UTC().Format(time.RFC3339), session.ID)
	return session, user, nil
}

func (r *Repository) RevokeSession(ctx context.Context, sessionID string) error {
	_, err := r.db.ExecContext(ctx, `UPDATE sessions SET revoked_at = ? WHERE id = ?`, time.Now().UTC().Format(time.RFC3339), sessionID)
	return err
}

func (r *Repository) CreatePasswordResetToken(ctx context.Context, token PasswordResetToken) error {
	tx, err := r.db.BeginTx(ctx, nil)
	if err != nil {
		return fmt.Errorf("begin password reset token: %w", err)
	}
	defer func() { _ = tx.Rollback() }()

	now := time.Now().UTC().Format(time.RFC3339)
	if _, err := tx.ExecContext(ctx, `
		UPDATE password_reset_tokens
		SET used_at = ?
		WHERE user_id = ? AND used_at IS NULL
	`, now, token.UserID); err != nil {
		return fmt.Errorf("expire prior password reset tokens: %w", err)
	}
	if _, err := tx.ExecContext(ctx, `
		INSERT INTO password_reset_tokens (id, user_id, token_hash, created_at, expires_at, initiated_by)
		VALUES (?, ?, ?, ?, ?, ?)
	`, token.ID, token.UserID, token.TokenHash, token.CreatedAt.Format(time.RFC3339), token.ExpiresAt.Format(time.RFC3339), nullString(token.InitiatedBy)); err != nil {
		return fmt.Errorf("create password reset token: %w", err)
	}
	if err := tx.Commit(); err != nil {
		return fmt.Errorf("commit password reset token: %w", err)
	}
	return nil
}

func (r *Repository) ExpirePasswordResetToken(ctx context.Context, tokenHash string) {
	_, _ = r.db.ExecContext(ctx, `
		UPDATE password_reset_tokens
		SET used_at = ?
		WHERE token_hash = ? AND used_at IS NULL
	`, time.Now().UTC().Format(time.RFC3339), tokenHash)
}

func (r *Repository) CompletePasswordReset(ctx context.Context, tokenHash, passwordHash string) (string, error) {
	tx, err := r.db.BeginTx(ctx, nil)
	if err != nil {
		return "", fmt.Errorf("begin password reset: %w", err)
	}
	defer func() { _ = tx.Rollback() }()

	var id, userID, expires, used string
	if err := tx.QueryRowContext(ctx, `
		SELECT id, user_id, expires_at, COALESCE(used_at, '')
		FROM password_reset_tokens
		WHERE token_hash = ?
	`, tokenHash).Scan(&id, &userID, &expires, &used); err != nil {
		return "", err
	}
	if used != "" {
		return "", fmt.Errorf("password reset link has already been used")
	}
	expiresAt, err := time.Parse(time.RFC3339, expires)
	if err != nil {
		return "", fmt.Errorf("parse password reset expiry: %w", err)
	}
	if time.Now().UTC().After(expiresAt) {
		return "", fmt.Errorf("password reset link has expired")
	}

	now := time.Now().UTC().Format(time.RFC3339)
	result, err := tx.ExecContext(ctx, `UPDATE password_reset_tokens SET used_at = ? WHERE id = ? AND used_at IS NULL`, now, id)
	if err != nil {
		return "", fmt.Errorf("consume password reset token: %w", err)
	}
	if rows, _ := result.RowsAffected(); rows == 0 {
		return "", fmt.Errorf("password reset link has already been used")
	}
	if _, err := tx.ExecContext(ctx, `UPDATE users SET password_hash = ? WHERE id = ?`, passwordHash, userID); err != nil {
		return "", fmt.Errorf("update reset password: %w", err)
	}
	if _, err := tx.ExecContext(ctx, `UPDATE sessions SET revoked_at = ? WHERE user_id = ? AND revoked_at IS NULL`, now, userID); err != nil {
		return "", fmt.Errorf("revoke password reset sessions: %w", err)
	}
	if err := tx.Commit(); err != nil {
		return "", fmt.Errorf("commit password reset: %w", err)
	}
	return userID, nil
}

func (r *Repository) CreateAPIKey(ctx context.Context, key APIKey) error {
	if _, err := r.db.ExecContext(ctx, `
		INSERT INTO api_keys (id, user_id, name, key_hash, scopes, created_at, expires_at, last_used_at, revoked_at)
		VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
	`, key.ID, key.UserID, key.Name, key.KeyHash, scopesToString(key.Scopes), key.CreatedAt.Format(time.RFC3339), timePtrString(key.ExpiresAt), timePtrString(key.LastUsedAt), timePtrString(key.RevokedAt)); err != nil {
		return fmt.Errorf("create api key: %w", err)
	}
	return nil
}

func (r *Repository) ValidateAPIKey(ctx context.Context, keyID, keyHash string) (APIKey, User, error) {
	var key APIKey
	var scopesRaw, created, expires, lastUsed, revoked string
	err := r.db.QueryRowContext(ctx, `
		SELECT id, user_id, name, key_hash, scopes, created_at, COALESCE(expires_at, ''), COALESCE(last_used_at, ''), COALESCE(revoked_at, '')
		FROM api_keys
		WHERE id = ?
	`, keyID).Scan(&key.ID, &key.UserID, &key.Name, &key.KeyHash, &scopesRaw, &created, &expires, &lastUsed, &revoked)
	if err != nil {
		return APIKey{}, User{}, err
	}
	if !constantTimeEqualHex(key.KeyHash, keyHash) {
		return APIKey{}, User{}, sql.ErrNoRows
	}
	key.Scopes = scopesFromString(scopesRaw)
	key.CreatedAt, err = time.Parse(time.RFC3339, created)
	if err != nil {
		return APIKey{}, User{}, err
	}
	if expires != "" {
		parsed, err := time.Parse(time.RFC3339, expires)
		if err != nil {
			return APIKey{}, User{}, err
		}
		if time.Now().UTC().After(parsed) {
			return APIKey{}, User{}, fmt.Errorf("api key expired")
		}
		key.ExpiresAt = &parsed
	}
	if revoked != "" {
		return APIKey{}, User{}, fmt.Errorf("api key revoked")
	}
	if lastUsed != "" {
		parsed, err := time.Parse(time.RFC3339, lastUsed)
		if err == nil {
			key.LastUsedAt = &parsed
		}
	}
	user, err := r.GetUserByID(ctx, key.UserID)
	if err != nil {
		return APIKey{}, User{}, err
	}
	_, _ = r.db.ExecContext(ctx, `UPDATE api_keys SET last_used_at = ? WHERE id = ?`, time.Now().UTC().Format(time.RFC3339), key.ID)
	return key, user, nil
}

func (r *Repository) ListAPIKeys(ctx context.Context, userID string) ([]APIKey, error) {
	rows, err := r.db.QueryContext(ctx, `
		SELECT id, user_id, name, scopes, created_at, COALESCE(expires_at, ''), COALESCE(last_used_at, ''), COALESCE(revoked_at, '')
		FROM api_keys
		WHERE user_id = ?
		ORDER BY created_at DESC
	`, userID)
	if err != nil {
		return nil, err
	}
	defer rows.Close()
	var keys []APIKey
	for rows.Next() {
		var key APIKey
		var scopesRaw, created, expires, lastUsed, revoked string
		if err := rows.Scan(&key.ID, &key.UserID, &key.Name, &scopesRaw, &created, &expires, &lastUsed, &revoked); err != nil {
			return nil, err
		}
		key.Scopes = scopesFromString(scopesRaw)
		key.CreatedAt, _ = time.Parse(time.RFC3339, created)
		if expires != "" {
			parsed, _ := time.Parse(time.RFC3339, expires)
			key.ExpiresAt = &parsed
		}
		if lastUsed != "" {
			parsed, _ := time.Parse(time.RFC3339, lastUsed)
			key.LastUsedAt = &parsed
		}
		if revoked != "" {
			parsed, _ := time.Parse(time.RFC3339, revoked)
			key.RevokedAt = &parsed
		}
		keys = append(keys, key)
	}
	return keys, rows.Err()
}

func (r *Repository) RevokeAPIKey(ctx context.Context, userID, keyID string) error {
	result, err := r.db.ExecContext(ctx, `UPDATE api_keys SET revoked_at = ? WHERE id = ? AND user_id = ?`, time.Now().UTC().Format(time.RFC3339), keyID, userID)
	if err != nil {
		return err
	}
	if rows, _ := result.RowsAffected(); rows == 0 {
		return sql.ErrNoRows
	}
	return nil
}

func (r *Repository) CreateDeviceCode(ctx context.Context, device DeviceCode) error {
	_, err := r.db.ExecContext(ctx, `
		INSERT INTO device_codes (id, device_code_hash, user_code_hash, user_code_display, name, scopes, status, created_at, expires_at)
		VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
	`, device.ID, device.DeviceCodeHash, device.UserCodeHash, device.UserCodeDisplay, device.Name, scopesToString(device.Scopes), device.Status, device.CreatedAt.Format(time.RFC3339), device.ExpiresAt.Format(time.RFC3339))
	return err
}

func (r *Repository) GetDeviceCodeByUserCode(ctx context.Context, userCodeHash string) (DeviceCode, error) {
	return r.getDeviceCode(ctx, `user_code_hash = ?`, userCodeHash)
}

func (r *Repository) GetDeviceCodeByDeviceCode(ctx context.Context, deviceCodeHash string) (DeviceCode, error) {
	return r.getDeviceCode(ctx, `device_code_hash = ?`, deviceCodeHash)
}

func (r *Repository) getDeviceCode(ctx context.Context, predicate string, value string) (DeviceCode, error) {
	var device DeviceCode
	var scopesRaw, created, expires, approved, lastPolled, userID string
	row := r.db.QueryRowContext(ctx, `
		SELECT id, COALESCE(user_id, ''), device_code_hash, user_code_hash, user_code_display, name, scopes, status, created_at, expires_at, COALESCE(approved_at, ''), COALESCE(last_polled_at, '')
		FROM device_codes
		WHERE `+predicate, value)
	if err := row.Scan(&device.ID, &userID, &device.DeviceCodeHash, &device.UserCodeHash, &device.UserCodeDisplay, &device.Name, &scopesRaw, &device.Status, &created, &expires, &approved, &lastPolled); err != nil {
		return DeviceCode{}, err
	}
	device.UserID = userID
	device.Scopes = scopesFromString(scopesRaw)
	device.CreatedAt, _ = time.Parse(time.RFC3339, created)
	device.ExpiresAt, _ = time.Parse(time.RFC3339, expires)
	if approved != "" {
		parsed, _ := time.Parse(time.RFC3339, approved)
		device.ApprovedAt = &parsed
	}
	if lastPolled != "" {
		parsed, _ := time.Parse(time.RFC3339, lastPolled)
		device.LastPolledAt = &parsed
	}
	if time.Now().UTC().After(device.ExpiresAt) && device.Status == "pending" {
		device.Status = "expired"
		_, _ = r.db.ExecContext(ctx, `UPDATE device_codes SET status = 'expired' WHERE id = ?`, device.ID)
	}
	return device, nil
}

func (r *Repository) ApproveDeviceCode(ctx context.Context, userCodeHash, userID string) (DeviceCode, error) {
	now := time.Now().UTC().Format(time.RFC3339)
	result, err := r.db.ExecContext(ctx, `
		UPDATE device_codes
		SET status = 'approved', user_id = ?, approved_at = ?
		WHERE user_code_hash = ? AND status = 'pending'
	`, userID, now, userCodeHash)
	if err != nil {
		return DeviceCode{}, err
	}
	if rows, _ := result.RowsAffected(); rows == 0 {
		return DeviceCode{}, sql.ErrNoRows
	}
	return r.GetDeviceCodeByUserCode(ctx, userCodeHash)
}

func (r *Repository) TouchDevicePoll(ctx context.Context, id string) {
	_, _ = r.db.ExecContext(ctx, `UPDATE device_codes SET last_polled_at = ? WHERE id = ?`, time.Now().UTC().Format(time.RFC3339), id)
}

func (r *Repository) FinishDeviceCode(ctx context.Context, id string) {
	_, _ = r.db.ExecContext(ctx, `UPDATE device_codes SET status = 'expired', last_polled_at = ? WHERE id = ?`, time.Now().UTC().Format(time.RFC3339), id)
}

func (r *Repository) Audit(ctx context.Context, actorUserID, eventType, resourceType, resourceID, ip, userAgent string, metadata map[string]any) {
	idPart, err := randomToken(18)
	if err != nil {
		return
	}
	if metadata == nil {
		metadata = map[string]any{}
	}
	payload, err := json.Marshal(metadata)
	if err != nil {
		payload = []byte("{}")
	}
	_, _ = r.db.ExecContext(ctx, `
		INSERT INTO audit_log (id, actor_user_id, event_type, resource_type, resource_id, ip_address, user_agent, created_at, metadata_json)
		VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
	`, "audit:"+idPart, nullString(actorUserID), eventType, nullString(resourceType), nullString(resourceID), ip, userAgent, time.Now().UTC().Format(time.RFC3339), string(payload))
}

func nullString(value string) any {
	if value == "" {
		return nil
	}
	return value
}

func boolInt(value bool) int {
	if value {
		return 1
	}
	return 0
}

func timePtrString(value *time.Time) any {
	if value == nil {
		return nil
	}
	return value.UTC().Format(time.RFC3339)
}

func isNoRows(err error) bool {
	return errors.Is(err, sql.ErrNoRows)
}
