import { Role, IdentityProvider, AuthEndpoints, UserAdminEndpoints } from '@nanoesis/engine';

/**
 * One user record. Every field is owned by the adapter, a custom UserStore must
 * round-trip them all. `loginCount` is the revocation counter (DESIGN §11): every
 * login/logout bumps it, and a JWT carrying a stale count is rejected; one cheap
 * UserStore read per request gets per-user revocation without a SessionStore.
 *
 * `failedAttempts` + `lockedUntil` drive the brute-force lockout (3 / 1 min per
 * DESIGN §11); they live alongside the credential so a custom store doesn't have
 * to ship its own attempt-tracking surface.
 */
interface UserRecord {
    /** Stable identifier (set once at create time, never reused). */
    readonly id: string;
    /** Login handle the user types. Case-insensitive lookups recommended at the store level. */
    readonly username: string;
    /**
     * Optional display name (bylines, account menu). Absent means "use {@link username}".
     * Stored trimmed; blank is normalised to absent by the provider.
     */
    readonly displayName?: string;
    readonly roles: readonly Role[];
    /** scrypt hash; opaque to consumers, produced by the adapter's `password` module. */
    readonly passwordHash: string;
    /** Revocation counter, bumped on every login and logout. */
    readonly loginCount: number;
    /** Consecutive failed login attempts since the last success. */
    readonly failedAttempts: number;
    /** Epoch ms; when set and in the future, the account is locked out. */
    readonly lockedUntil?: number;
    /**
     * Personal-access-token revocation counter (DESIGN §14). PATs (the MCP HTTP transport)
     * carry this value; bumping it revokes every PAT for the user, independent of the login
     * session (so editor logins never invalidate a PAT). Absent is treated as 0.
     */
    readonly patVersion?: number;
    /** Epoch ms; record creation time. */
    readonly createdAt: number;
    /** Epoch ms; last write time. */
    readonly updatedAt: number;
}
/**
 * The credential store the local-JWT provider talks to. A user supplies a concrete
 * implementation (file-backed is shipped; bring your own for blob/cosmos/sql). The
 * port is deliberately small, every method maps onto one auth operation.
 */
interface UserStore {
    /** True when no records exist; drives the first-run-becomes-admin path (DESIGN §11). */
    isEmpty(): Promise<boolean>;
    getByUsername(username: string): Promise<UserRecord | null>;
    getById(id: string): Promise<UserRecord | null>;
    /** Insert or replace by `id`. */
    put(user: UserRecord): Promise<void>;
    remove(id: string): Promise<void>;
    /** All records, in deterministic order, for admin "list users" tooling. */
    list(): Promise<readonly UserRecord[]>;
}
/**
 * An ephemeral in-process {@link UserStore}. Useful for tests and ephemeral examples;
 * never persistent. Lookups by username are case-insensitive.
 */
declare class InMemoryUserStore implements UserStore {
    private readonly byId;
    isEmpty(): Promise<boolean>;
    getByUsername(username: string): Promise<UserRecord | null>;
    getById(id: string): Promise<UserRecord | null>;
    put(user: UserRecord): Promise<void>;
    remove(id: string): Promise<void>;
    list(): Promise<readonly UserRecord[]>;
}
/**
 * A JSON-file {@link UserStore} for the local example and small self-hosted deploys.
 * The whole file is read and rewritten atomically (write to `.tmp`, rename) so a crash
 * mid-write leaves the previous good file. Suitable up to "tens of users on one
 * process"; production deploys with real persistence should bring their own
 * implementation of {@link UserStore}.
 *
 * The file is `{"users": UserRecord[]}`, versionable JSON, hand-editable in a pinch.
 */
declare class FileUserStore implements UserStore {
    private readonly path;
    constructor(path: string);
    private load;
    private save;
    isEmpty(): Promise<boolean>;
    getByUsername(username: string): Promise<UserRecord | null>;
    getById(id: string): Promise<UserRecord | null>;
    put(user: UserRecord): Promise<void>;
    remove(id: string): Promise<void>;
    list(): Promise<readonly UserRecord[]>;
}

/** Thrown when a password is rejected at hash time (before the work). */
declare class WeakPasswordError extends Error {
    constructor(reason: string);
}
/**
 * Hash a password with scrypt and return a self-describing string of the form
 * `scrypt$1$<base64 salt>$<base64 hash>`. Re-derivable later by `verifyPassword`
 * with no other state, every parameter the hash needs is encoded in the prefix.
 *
 * Rejects an empty password (a kindness to the caller; the routes layer is expected to
 * also require a non-empty password on the way in). No length floor is imposed, the
 * deployment owns its password policy.
 */
declare function hashPassword(password: string): Promise<string>;
/**
 * Constant-time check of `password` against an `encoded` hash produced by
 * {@link hashPassword}. Returns false on every shape of mismatch, wrong password,
 * truncated/garbage hash string, unknown version, so the caller never has to branch
 * on "is this hash valid". Constant time matters here: it's the login hot path.
 */
declare function verifyPassword(password: string, encoded: string): Promise<boolean>;

/**
 * The JWT payload nanoesis issues. Compact on purpose, every byte is sent on every
 * authenticated request. `lc` is the loginCount revocation counter (DESIGN §11),
 * compared against the user record on `authenticate` and `refresh`.
 */
interface JwtPayload {
    /** Subject: the user record id. */
    readonly sub: string;
    /** Roles snapshot at issue time. */
    readonly roles: readonly Role[];
    /** loginCount when the token was issued, revocation handle for session tokens. */
    readonly lc: number;
    /** Issued-at epoch seconds. */
    readonly iat: number;
    /** Expiry epoch seconds. */
    readonly exp: number;
    /** Marks a personal access token (DESIGN §14): validated against `patVersion`, not `lc`. */
    readonly pat?: true;
    /** patVersion at issue time (PATs only); the revocation handle independent of loginCount. */
    readonly pv?: number;
}
/** Distinct failure modes for `verifyJwt`, the caller maps them to HTTP status. */
type JwtVerifyError = 'malformed' | 'bad-signature' | 'expired';
type JwtVerifyResult = {
    readonly ok: true;
    readonly payload: JwtPayload;
} | {
    readonly ok: false;
    readonly reason: JwtVerifyError;
};
/**
 * Sign a JWT (compact serialization, HS256). Standard layout
 * `base64url(header).base64url(payload).base64url(HMAC-SHA256(header.payload))`, no
 * external dep, just `node:crypto`. The secret is opaque bytes; pick at least 32 of
 * them from a CSPRNG.
 */
declare function signJwt(payload: JwtPayload, secret: string | Buffer): string;
/**
 * Verify a JWT and return either the payload or a structured reason for the rejection.
 * Never throws on bad input, a malformed token returns `{ ok: false, reason: 'malformed' }`,
 * which matches how the rest of the host handles boundary failures (data, not exceptions).
 *
 * `nowSeconds` is injected for tests so we can verify the expiry boundary without
 * sleeping, and so the rest of the adapter doesn't need a Clock port.
 */
declare function verifyJwt(token: string, secret: string | Buffer, nowSeconds?: number): JwtVerifyResult;

/**
 * Configuration for {@link createLocalJwtAuth}. The defaults match DESIGN §11, 10-min
 * access tokens forcing a re-check of the loginCount, 3 failed attempts before a
 * 1-minute lockout, and bumping loginCount on every login/logout so a previously-
 * issued token dies the next time its owner logs in.
 *
 * `now` and `newId` are injected for tests so the lockout windows and id generation
 * are deterministic; production should leave them at their defaults.
 */
interface LocalJwtConfig {
    readonly users: UserStore;
    /** HMAC-SHA256 secret. At least 32 bytes of CSPRNG output. */
    readonly signingSecret: string | Buffer;
    /** Access-token TTL in seconds. Default 600 (10 min). */
    readonly accessTokenTtlSeconds?: number;
    /** Personal-access-token (PAT) TTL in seconds. Long by design, a PAT lives in a client
     *  config (e.g. an MCP client). Default 90 days. */
    readonly patTtlSeconds?: number;
    /**
     * Refresh grace window in seconds. A token whose `exp` has passed is still accepted
     * by `refresh()` as long as it's within this window AND signature + loginCount check
     * out. This makes the access TTL a "must refresh" deadline rather than a "must
     * re-login" one, a user who closes their laptop for the weekend still gets a
     * silent refresh on return, while revocation (loginCount mismatch) and explicit
     * logout still force a re-login. Default 30 days (60 * 60 * 24 * 30).
     */
    readonly refreshGraceSeconds?: number;
    /** Failed attempts before a lockout kicks in. Default 3. */
    readonly maxFailedAttempts?: number;
    /** Lockout duration once the threshold trips. Default 60s. */
    readonly lockoutSeconds?: number;
    /** Injected for tests; defaults to `Date.now()`. */
    readonly now?: () => number;
    /** Injected for tests; defaults to `crypto.randomUUID()`. */
    readonly newId?: () => string;
}
declare function createLocalJwtAuth(config: LocalJwtConfig): {
    readonly identity: IdentityProvider;
    readonly endpoints: AuthEndpoints;
    readonly userAdmin: UserAdminEndpoints;
};

export { FileUserStore, InMemoryUserStore, type JwtPayload, type JwtVerifyError, type JwtVerifyResult, type LocalJwtConfig, type UserRecord, type UserStore, WeakPasswordError, createLocalJwtAuth, hashPassword, signJwt, verifyJwt, verifyPassword };
