import type { Context } from "@wrnexus/core";

export type AuthIdentityType = "email" | "phone" | "username";
export type AuthTokenPurpose =
  | "verify-email"
  | "verify-phone"
  | "password-reset"
  | "magic-link"
  | "invite"
  | "change-email"
  | "change-phone"
  | "login-mfa";
export type AuthMfaMethod = "email-otp" | "sms-otp" | "totp" | "recovery-code" | "passkey";
export type AuthAccountStatus = "pending" | "active" | "locked" | "disabled" | "deleted";
export type AuthRiskLevel = "low" | "medium" | "high" | "critical";

export interface AuthUser {
  id: string;
  username?: string;
  displayName?: string;
  avatarUrl?: string;
  status: AuthAccountStatus;
  roles: string[];
  emailVerified: boolean;
  phoneVerified: boolean;
  mfaEnabled: boolean;
  locale?: string;
  timezone?: string;
  createdAt: number;
  updatedAt: number;
  lastLoginAt?: number;
  metadata?: Record<string, unknown>;
}

export interface AuthIdentity {
  id: string;
  userId: string;
  type: AuthIdentityType;
  value: string;
  normalizedValue: string;
  primary: boolean;
  verifiedAt?: number;
  createdAt: number;
  updatedAt: number;
}

export interface PasswordCredential {
  userId: string;
  passwordHash: string;
  passwordVersion: number;
  changedAt: number;
  mustChange: boolean;
}

export interface AuthSession {
  id: string;
  userId: string;
  deviceId: string;
  createdAt: number;
  lastSeenAt: number;
  expiresAt: number;
  absoluteExpiresAt: number;
  ip?: string;
  userAgent?: string;
  trusted: boolean;
  revokedAt?: number;
  revokeReason?: string;
  metadata?: Record<string, unknown>;
}

export interface TrustedDevice {
  id: string;
  userId: string;
  name: string;
  fingerprintHash: string;
  createdAt: number;
  lastSeenAt: number;
  expiresAt: number;
  revokedAt?: number;
}

export interface OneTimeToken {
  id: string;
  userId: string;
  purpose: AuthTokenPurpose;
  tokenHash: string;
  target?: string;
  createdAt: number;
  expiresAt: number;
  usedAt?: number;
  attempts: number;
  maxAttempts: number;
  metadata?: Record<string, unknown>;
}

export interface OtpChallenge {
  id: string;
  userId: string;
  method: "email-otp" | "sms-otp";
  purpose: "verification" | "login" | "mfa";
  destination: string;
  codeHash: string;
  createdAt: number;
  expiresAt: number;
  usedAt?: number;
  attempts: number;
  maxAttempts: number;
}

export interface TotpCredential {
  id: string;
  userId: string;
  label: string;
  secret: string;
  createdAt: number;
  verifiedAt?: number;
  lastCounter?: number;
}

export interface RecoveryCodeRecord {
  id: string;
  userId: string;
  codeHash: string;
  createdAt: number;
  usedAt?: number;
}

export interface PasskeyCredential {
  id: string;
  userId: string;
  credentialId: string;
  publicKey: string;
  counter: number;
  transports: string[];
  name: string;
  createdAt: number;
  lastUsedAt?: number;
  backedUp?: boolean;
  deviceType?: string;
}

export interface OAuthAccount {
  id: string;
  userId: string;
  provider: string;
  providerAccountId: string;
  email?: string;
  accessToken?: string;
  refreshToken?: string;
  tokenExpiresAt?: number;
  scope?: string;
  createdAt: number;
  updatedAt: number;
}

export interface LoginAttempt {
  id: string;
  identifier?: string;
  userId?: string;
  success: boolean;
  reason?: string;
  ip?: string;
  userAgent?: string;
  createdAt: number;
  riskScore: number;
  riskLevel: AuthRiskLevel;
}

export const AUTH_SECURITY_EVENT_TYPES = [
  "account.registered",
  "account.status-changed",
  "delivery.failed",
  "device.revoked",
  "device.trusted",
  "identity.email-verification-requested",
  "identity.email-verified",
  "identity.otp-verified",
  "identity.phone-verification-requested",
  "identity.phone-verified",
  "impersonation.denied",
  "impersonation.ended",
  "impersonation.started",
  "invitation.accepted",
  "invitation.created",
  "login.failed",
  "login.magic-link",
  "login.oauth",
  "login.otp",
  "login.otp-verified",
  "login.passkey",
  "login.succeeded",
  "mfa.failed",
  "mfa.otp-verified",
  "mfa.recovery-code-used",
  "mfa.recovery-codes-generated",
  "mfa.succeeded",
  "mfa.totp-disabled",
  "mfa.totp-enabled",
  "mfa.totp-verified",
  "oauth.linked",
  "oauth.unlinked",
  "passkey.registered",
  "password.changed",
  "password.reset",
  "password.reset-requested",
  "session.revoked",
  "session.revoked-all",
] as const;

export type KnownAuthSecurityEventType = (typeof AUTH_SECURITY_EVENT_TYPES)[number];
/** Known framework events plus application-defined extension events. */
export type AuthSecurityEventType = KnownAuthSecurityEventType | (string & {});

export interface AuthSecurityEvent {
  id: string;
  userId?: string;
  type: AuthSecurityEventType;
  severity: "info" | "warning" | "critical";
  actorUserId?: string;
  sessionId?: string;
  ip?: string;
  userAgent?: string;
  createdAt: number;
  data?: Record<string, unknown>;
}

export interface AuthRiskSignals {
  failedAttempts?: number;
  unfamiliarDevice?: boolean;
  unusualIp?: boolean;
  impossibleTravel?: boolean;
  breachedPassword?: boolean;
  automationSuspected?: boolean;
  accountLocked?: boolean;
  customScore?: number;
}

export interface AuthRiskDecision {
  score: number;
  level: AuthRiskLevel;
  requireCaptcha: boolean;
  requireMfa: boolean;
  block: boolean;
  reasons: string[];
}

export interface AuthPublicUser {
  id: string;
  username?: string;
  displayName?: string;
  avatarUrl?: string;
  status: AuthAccountStatus;
  roles: string[];
  emailVerified: boolean;
  phoneVerified: boolean;
  mfaEnabled: boolean;
}

export interface AuthDeliveryMessage {
  channel: "email" | "sms";
  template:
    | "verify-email"
    | "verify-phone"
    | "password-reset"
    | "magic-link"
    | "email-otp"
    | "sms-otp"
    | "login-alert"
    | "invitation";
  destination: string;
  code?: string;
  token?: string;
  url?: string;
  user: AuthPublicUser;
  expiresAt: number;
  data?: Record<string, unknown>;
}

export interface AuthDeliveryProvider {
  send(message: AuthDeliveryMessage): Promise<void>;
}

/** HTTP response hook invoked after the auth engine establishes a signed-in session. */
export type AuthSignedInHandler = (ctx: Context, returnTo?: string) => Response | Promise<Response>;

/** HTTP response hook invoked after the auth engine clears a signed-in session. */
export type AuthSignedOutHandler = (ctx: Context) => Response | Promise<Response>;

/** Forward-auth response hook. A null user means the request is unauthenticated. */
export type AuthSessionVerificationHandler = (
  ctx: Context,
  user: AuthPublicUser | null,
) => Response | Promise<Response>;

export interface AuthSuccessfulSignUpAction {
  /**
   * Run the normal login policy with the newly registered credentials and
   * establish a session when verification, CAPTCHA, MFA, and account policy
   * allow it.
   */
  autoSignIn?: boolean;
  /** Same-origin path used after signup. Defaults to /account for auto sign-in and /sign-in otherwise. */
  redirectTo?: string;
}

export type AuthSuccessfulSignUpHandler = (
  ctx: Context,
  user: AuthPublicUser,
) =>
  | AuthSuccessfulSignUpAction
  | Response
  | void
  | Promise<AuthSuccessfulSignUpAction | Response | void>;

/** Input used to build links placed in verification, recovery, magic-link, and invitation messages. */
export interface AuthTokenUrlInput {
  purpose: AuthTokenPurpose;
  token: string;
  baseUrl?: string;
  destination: string;
  user: AuthPublicUser;
}

/** Encrypts sensitive authentication material before persistence. */
export interface AuthSecretProtector {
  protect(value: string, purpose: "totp" | "oauth-access" | "oauth-refresh"): Promise<string>;
  reveal(value: string, purpose: "totp" | "oauth-access" | "oauth-refresh"): Promise<string>;
}

export interface AuthImpersonationDecision {
  allowed: boolean;
  reason?: string;
}

export interface PasswordBreachProvider {
  isBreached(password: string): Promise<boolean>;
}

export interface PasskeyRegistrationOptions {
  challenge: string;
  rp: { id: string; name: string };
  user: { id: string; name: string; displayName: string };
  timeout: number;
  attestation: "none" | "direct" | "enterprise";
  authenticatorSelection?: Record<string, unknown>;
  excludeCredentials?: Array<{ id: string; type: "public-key"; transports?: string[] }>;
}

export interface PasskeyAuthenticationOptions {
  challenge: string;
  rpId: string;
  timeout: number;
  userVerification: "required" | "preferred" | "discouraged";
  allowCredentials?: Array<{ id: string; type: "public-key"; transports?: string[] }>;
}

export interface PasskeyVerificationResult {
  verified: boolean;
  credential?: Omit<PasskeyCredential, "id" | "userId" | "createdAt">;
  credentialId?: string;
  newCounter?: number;
  userId?: string;
  error?: string;
}

export interface PasskeyProvider {
  registrationOptions(input: {
    user: AuthUser;
    identities: AuthIdentity[];
    credentials: PasskeyCredential[];
    rpId: string;
    rpName: string;
    origin: string;
  }): Promise<PasskeyRegistrationOptions>;
  verifyRegistration(input: {
    user: AuthUser;
    response: unknown;
    expectedChallenge: string;
    expectedOrigin: string;
    expectedRpId: string;
  }): Promise<PasskeyVerificationResult>;
  authenticationOptions(input: {
    user?: AuthUser;
    credentials: PasskeyCredential[];
    rpId: string;
    origin: string;
  }): Promise<PasskeyAuthenticationOptions>;
  verifyAuthentication(input: {
    response: unknown;
    credential?: PasskeyCredential;
    expectedChallenge: string;
    expectedOrigin: string;
    expectedRpId: string;
  }): Promise<PasskeyVerificationResult>;
}

export interface AuthClock {
  now(): number;
}

export interface AuthRandom {
  bytes(length: number): Uint8Array;
}

export interface AuthEngineOptions {
  store: import("./store.ts").AuthStore;
  secret: string;
  issuer?: string;
  delivery?: AuthDeliveryProvider;
  /**
   * Customize the HTTP response after any package sign-in flow succeeds.
   * Keeping this beside delivery and tokenUrl makes the engine the single
   * location for authentication behavior.
   */
  onSignedIn?: AuthSignedInHandler;
  /** Customize the HTTP response after a package logout succeeds. */
  onSignedOut?: AuthSignedOutHandler;
  /**
   * Customize successful package registration. Without this hook, signup
   * redirects to /sign-in.
   */
  onSuccessfulSignUp?: AuthSuccessfulSignUpHandler;
  /** @deprecated Misspelled alias; use onSuccessfulSignUp. */
  onSuccessfullSignUp?: AuthSuccessfulSignUpHandler;
  breachProvider?: PasswordBreachProvider;
  passkeys?: PasskeyProvider;
  passkeyChallengeStore?: import("./passkeys/index.ts").PasskeyChallengeStore;
  clock?: AuthClock;
  random?: AuthRandom;
  sessionTtlMs?: number;
  sessionAbsoluteTtlMs?: number;
  trustedDeviceTtlMs?: number;
  tokenTtlMs?: Partial<Record<AuthTokenPurpose, number>>;
  /**
   * Customize action links delivered with one-time tokens. Returning undefined
   * intentionally omits the URL while still delivering the raw token.
   */
  tokenUrl?: (input: AuthTokenUrlInput) => string | undefined;
  otpTtlMs?: number;
  maxTokenAttempts?: number;
  maxOtpAttempts?: number;
  maxFailedLogins?: number;
  lockDurationMs?: number;
  passwordMinLength?: number;
  requireVerifiedEmail?: boolean;
  requireVerifiedPhone?: boolean;
  captchaThreshold?: number;
  mfaThreshold?: number;
  blockThreshold?: number;
  skipMfaForTrustedDevices?: boolean;
  sendLoginAlerts?: boolean;
  secretProtector?: AuthSecretProtector;
  linkVerifiedOAuthEmails?: boolean;
  isOAuthEmailVerified?: (
    provider: string,
    profile: import("@wrnexus/oauth").OAuthProfile,
  ) => boolean | Promise<boolean>;
  authorizeImpersonation?: (input: {
    actor: AuthUser;
    target: AuthUser;
    reason?: string;
  }) => boolean | AuthImpersonationDecision | Promise<boolean | AuthImpersonationDecision>;
  audit?: (event: AuthSecurityEvent) => void | Promise<void>;
}

export interface RegisterInput {
  email?: string;
  phone?: string;
  username?: string;
  password: string;
  displayName?: string;
  locale?: string;
  timezone?: string;
  metadata?: Record<string, unknown>;
}

export interface LoginInput {
  identifier: string;
  password: string;
  ip?: string;
  userAgent?: string;
  deviceId?: string;
  deviceName?: string;
  fingerprint?: string;
  rememberDevice?: boolean;
  captchaVerified?: boolean;
  signals?: AuthRiskSignals;
}

export interface AuthResult {
  ok: boolean;
  code?: string;
  message?: string;
  user?: AuthPublicUser;
  session?: AuthSession;
  risk?: AuthRiskDecision;
  mfaToken?: string;
  requires?: {
    captcha?: boolean;
    mfa?: AuthMfaMethod[];
    emailVerification?: boolean;
    phoneVerification?: boolean;
  };
}

export interface AuthenticatedContext extends Context {
  user: AuthPublicUser;
  locals: Context["locals"] & {
    authUser: AuthPublicUser;
    authSession?: AuthSession;
  };
}
