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

export type CaptchaProviderName =
  "self-hosted" | "wrnexus-managed" | "turnstile" | "recaptcha" | "hcaptcha" | (string & {});

export type CaptchaChallengeType =
  | "random"
  | "number"
  | "alpha"
  | "alphanumeric"
  | "calculation"
  | "image"
  | "honeypot"
  | "timing"
  | "not-robot"
  | (string & {});

export type CaptchaDifficulty = "easy" | "normal" | "hard";
export type CaptchaConcreteImageStyle =
  | "classic"
  | "collision"
  | "snow"
  | "corrosion"
  | "spiderweb"
  | "cross-shadow"
  | "split"
  | "split2"
  | "cut"
  | "darts"
  | "distortion"
  | "stitch"
  | "striped"
  | "wave"
  | "grid-noise"
  | "scribble"
  | "pixel"
  | "broken-lines";
export type CaptchaImageStyle = "random" | CaptchaConcreteImageStyle;
export type CaptchaPresentation = "visual" | "audio" | "invisible";
export type CaptchaPolicyMode = "always" | "session" | "adaptive" | "never";

export type CaptchaFailureCode =
  | "missing-input"
  | "invalid-input"
  | "incorrect-answer"
  | "expired"
  | "already-used"
  | "attempts-exhausted"
  | "action-mismatch"
  | "hostname-mismatch"
  | "session-mismatch"
  | "ip-mismatch"
  | "provider-error"
  | "network-error"
  | "risk-rejected"
  | "internal-error";

export interface CaptchaBinding {
  hostname?: string;
  sessionId?: string;
  ip?: string;
}

export interface CaptchaImageItem {
  id: string;
  image: string;
  alt?: string;
}

export interface CaptchaChallenge {
  id: string;
  provider: CaptchaProviderName;
  type: CaptchaChallengeType;
  presentation: CaptchaPresentation;
  action: string;
  prompt: string;
  createdAt: number;
  expiresAt: number;
  responseField: string;
  inputMode?: "text" | "numeric" | "none";
  image?: string;
  audioUrl?: string;
  refreshUrl?: string;
  verifyUrl?: string;
  items?: CaptchaImageItem[];
  minSelections?: number;
  maxSelections?: number;
  honeypotField?: string;
  timingToken?: string;
  metadata?: Record<string, unknown>;
}

export interface CreateCaptchaOptions extends CaptchaBinding {
  action: string;
  type?: CaptchaChallengeType;
  presentation?: CaptchaPresentation;
  difficulty?: CaptchaDifficulty;
  /** Visual disturbance percentage. 25 is easiest and 75 is hardest. */
  disturbance?: number;
  /** Renderer used for generated text and calculation CAPTCHA images. */
  imageStyle?: CaptchaImageStyle;
  /** Optional renderer pool, supplied as an array or comma-separated string. */
  allowedStyles?: CaptchaConcreteImageStyle[] | string;
  /** Renderers removed from the active pool. */
  excludedStyles?: CaptchaConcreteImageStyle[] | string;
  /** Force a new random renderer even when imageStyle names a concrete style. */
  randomizeStyle?: boolean;
  locale?: string;
  length?: number;
  caseSensitive?: boolean;
  expiresInMs?: number;
  maxAttempts?: number;
  minCompletionMs?: number;
  responseField?: string;
  metadata?: Record<string, unknown>;
}

export interface VerifyCaptchaInput extends CaptchaBinding {
  challengeId?: string;
  responseToken?: string;
  providerToken?: string;
  answer?: string | number;
  selections?: string[];
  action: string;
  honeypot?: string;
  timingToken?: string;
  consume?: boolean;
}

export interface CaptchaVerificationResult {
  success: boolean;
  provider: CaptchaProviderName;
  action: string;
  code?: CaptchaFailureCode | string;
  message?: string;
  responseToken?: string;
  expiresAt?: number;
  score?: number;
  hostname?: string;
  challengeId?: string;
  metadata?: Record<string, unknown>;
}

export interface CaptchaChallengeRecord {
  id: string;
  provider: CaptchaProviderName;
  type: CaptchaChallengeType;
  presentation: CaptchaPresentation;
  action: string;
  publicChallenge: CaptchaChallenge;
  answerDigest: string;
  answerSalt: string;
  answerKind: "text" | "selections" | "invisible";
  caseSensitive: boolean;
  createdAt: number;
  expiresAt: number;
  attempts: number;
  maxAttempts: number;
  consumedAt?: number;
  hostnameHash?: string;
  sessionHash?: string;
  ipHash?: string;
  metadata: Record<string, unknown>;
}

export interface CaptchaResponseTokenRecord {
  tokenHash: string;
  provider: CaptchaProviderName;
  challengeId?: string;
  action: string;
  createdAt: number;
  expiresAt: number;
  consumedAt?: number;
  hostnameHash?: string;
  sessionHash?: string;
  ipHash?: string;
  score?: number;
  metadata?: Record<string, unknown>;
}

export interface CaptchaStore {
  createChallenge(record: CaptchaChallengeRecord): Promise<void>;
  getChallenge(id: string): Promise<CaptchaChallengeRecord | undefined>;
  incrementAttempts(id: string, now: number): Promise<CaptchaChallengeRecord | undefined>;
  consumeChallenge(id: string, now: number): Promise<CaptchaChallengeRecord | undefined>;
  deleteChallenge(id: string): Promise<void>;
  createToken(record: CaptchaResponseTokenRecord): Promise<void>;
  getToken(tokenHash: string): Promise<CaptchaResponseTokenRecord | undefined>;
  consumeToken(tokenHash: string, now: number): Promise<CaptchaResponseTokenRecord | undefined>;
  deleteToken(tokenHash: string): Promise<void>;
  gc?(now: number): Promise<void>;
}

export interface GeneratedCaptchaChallenge {
  type: CaptchaChallengeType;
  presentation: CaptchaPresentation;
  prompt: string;
  answer: string;
  answerKind: CaptchaChallengeRecord["answerKind"];
  image?: string;
  items?: CaptchaImageItem[];
  minSelections?: number;
  maxSelections?: number;
  inputMode?: CaptchaChallenge["inputMode"];
  audioSequence?: string[];
  metadata?: Record<string, unknown>;
}

export interface CaptchaGeneratorContext {
  difficulty: CaptchaDifficulty;
  /** Normalized visual disturbance percentage in the inclusive range 25..75. */
  disturbance: number;
  /** Concrete renderer selected for this challenge. */
  imageStyle: CaptchaConcreteImageStyle;
  /** Renderer requested by the caller before random resolution. */
  requestedImageStyle: CaptchaImageStyle;
  /** Concrete renderer pool available to random selection. */
  imageStylePool: readonly CaptchaConcreteImageStyle[];
  locale: string;
  length?: number;
  caseSensitive: boolean;
  minCompletionMs: number;
  randomInt(min: number, max: number): number;
  randomFloat(): number;
  randomId(bytes?: number): string;
}

export interface CaptchaChallengeGenerator {
  readonly type: CaptchaChallengeType;
  generate(
    context: CaptchaGeneratorContext,
  ): GeneratedCaptchaChallenge | Promise<GeneratedCaptchaChallenge>;
}

export interface CaptchaAudioRenderer {
  render(sequence: string[], locale: string): Promise<Uint8Array>;
  contentType?: string;
}

export interface CaptchaEngineOptions {
  secret: string;
  store?: CaptchaStore;
  generators?: CaptchaChallengeGenerator[];
  audioRenderer?: CaptchaAudioRenderer;
  basePath?: string;
  challengeTtlMs?: number;
  responseTokenTtlMs?: number;
  maxAttempts?: number;
  minCompletionMs?: number;
  responseField?: string;
  defaultType?: CaptchaChallengeType;
  defaultDifficulty?: CaptchaDifficulty;
  bindIp?: boolean;
  now?: () => number;
  randomBytes?: (length: number) => Uint8Array;
}

export interface CaptchaEngine {
  readonly provider: "self-hosted";
  readonly basePath: string;
  create(options: CreateCaptchaOptions): Promise<CaptchaChallenge>;
  verify(input: VerifyCaptchaInput): Promise<CaptchaVerificationResult>;
  verifyResponseToken(input: VerifyCaptchaInput): Promise<CaptchaVerificationResult>;
  renderAudio(
    challengeId: string,
    key: string,
  ): Promise<{ bytes: Uint8Array; contentType: string } | undefined>;
  gc(): Promise<void>;
}

export interface CaptchaProviderClientConfig {
  responseField: string;
  siteKey?: string;
  scriptUrl?: string;
  widgetClass?: string;
  managedCreateUrl?: string;
}

export interface CaptchaProvider {
  readonly name: CaptchaProviderName;
  readonly client: CaptchaProviderClientConfig;
  createChallenge?(options: CreateCaptchaOptions): Promise<CaptchaChallenge>;
  verify(input: VerifyCaptchaInput): Promise<CaptchaVerificationResult>;
}

export interface CaptchaRiskSignals {
  failedAttempts?: number;
  requestsInWindow?: number;
  completionMs?: number;
  missingBrowserSignals?: boolean;
  suspiciousHeaders?: boolean;
  tokenReuse?: boolean;
  knownBadIp?: boolean;
  customScore?: number;
}

export interface CaptchaRiskResult {
  score: number;
  challenge: boolean;
  reasons: string[];
}

export interface CaptchaPolicyOptions {
  mode?: CaptchaPolicyMode;
  threshold?: number;
  verifiedForMs?: number;
  alwaysForActions?: string[];
  neverForActions?: string[];
  routeGroups?: string[];
}

export interface CaptchaGuardOptions {
  action: string | ((ctx: Context) => string);
  responseField?: string;
  provider?: CaptchaProvider;
  engine?: CaptchaEngine;
  /**
   * Keep successful verification bound to the current session and action for
   * this duration. Useful for retryable operations such as login, where a
   * valid CAPTCHA should not be repeated after an unrelated credential error.
   * Defaults to 0 (every request needs its own response token).
   */
  verifiedForMs?: number;
  /** Session key used for verified grants. */
  sessionKey?: string;
  failureStatus?: number;
  failureMessage?: string;
  bindHostname?: boolean;
  bindSession?: boolean;
  bindIp?: boolean;
  onFailure?: (ctx: Context, result: CaptchaVerificationResult) => Response | Promise<Response>;
}

export interface CaptchaPageGateOptions extends CaptchaGuardOptions {
  policy?: CaptchaPolicyOptions;
  challengePath?: string;
  returnToParam?: string;
  signals?: (ctx: Context) => CaptchaRiskSignals | Promise<CaptchaRiskSignals>;
}

export type CaptchaMiddleware = Middleware;

export interface CaptchaHttpHandlers {
  handle(request: Request, ctx?: Context): Promise<Response | undefined>;
  create(request: Request, ctx?: Context): Promise<Response>;
  verify(request: Request, ctx?: Context): Promise<Response>;
  audio(request: Request, ctx?: Context): Promise<Response>;
}
