import { v, type ObjectSchema, type SchemaDescriptor } from "@wrnexus/validation";

const strongPassword = () =>
  v
    .string()
    .required("Enter your password")
    .min(12, "Password must be at least 12 characters")
    .max(256, "Password must be at most 256 characters")
    .pattern(
      /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d).+$/,
      "Password must include uppercase, lowercase, and a number",
    );

/** Generic server-side registration input for custom registration experiences. */
export const registerSchema = v.object({
  displayName: v.string().trim().min(2, "Enter your full name").max(120),
  email: v.string().trim().email("Enter a valid email address").optional(),
  phone: v.string().trim().min(7, "Enter a valid phone number").max(24).optional(),
  username: v
    .string()
    .trim()
    .min(3, "Username must be at least 3 characters")
    .max(64)
    .pattern(/^[a-zA-Z0-9._-]+$/, "Use only letters, numbers, dots, underscores, or hyphens")
    .optional(),
  password: strongPassword(),
  locale: v.string().max(32).optional(),
  timezone: v.string().max(64).optional(),
});

/** Default schema shared by the packaged SignUp component and register route. */
export const signUpSchema = registerSchema.extend({
  email: v
    .string()
    .trim()
    .required("Enter your email address")
    .email("Enter a valid email address"),
  consent: v.boolean().required("Accept the terms and privacy policy to continue"),
});

export const loginSchema = v.object({
  identifier: v.string().trim().required("Enter your email, phone, or username").max(320),
  password: v.string().required("Enter your password").max(256),
  returnTo: v.string().max(2048).optional(),
  rememberDevice: v.boolean().optional(),
  deviceFingerprint: v.string().max(512).optional(),
  deviceName: v.string().max(120).optional(),
});

export const verificationRequestSchema = v.object({
  type: v
    .string()
    .required("Choose email or phone verification")
    .oneOf(["email", "phone"], "Choose email or phone verification"),
  identifier: v.string().trim().max(320).optional(),
});

export const verificationTokenSchema = v.object({
  token: v
    .string()
    .trim()
    .required("Enter the verification token")
    .min(6, "Verification token is too short")
    .max(512),
});

export const passwordResetRequestSchema = v.object({
  identifier: v
    .string()
    .trim()
    .required("Enter your email, phone, or username")
    .max(320, "Account identifier is too long"),
});

export const passwordResetSchema = v.object({
  token: v
    .string()
    .required("Password reset token is missing")
    .min(20, "Password reset token is invalid")
    .max(512),
  password: strongPassword(),
});

export const invitationAcceptSchema = v.object({
  token: v
    .string()
    .required("Invitation token is missing")
    .min(20, "Invitation token is invalid")
    .max(512),
  displayName: v.string().trim().min(2, "Enter your full name").max(120).optional(),
  password: strongPassword().optional(),
});

export const magicLinkRequestSchema = v.object({
  identifier: v.string().trim().required("Enter your email, phone, or username").max(320),
});

export const magicLinkConsumeSchema = v.object({
  token: v
    .string()
    .required("Magic-link token is missing")
    .min(20, "Magic-link token is invalid")
    .max(512),
  returnTo: v.string().max(2048).optional(),
});

export const otpLoginRequestSchema = v.object({
  identifier: v.string().trim().required("Enter your email, phone, or username").max(320),
  method: v
    .string()
    .required("Choose an OTP delivery method")
    .oneOf(["email-otp", "sms-otp"], "Choose email or SMS OTP"),
});

export const otpLoginCompleteSchema = v.object({
  challengeId: v
    .string()
    .required("OTP challenge is missing")
    .min(8, "OTP challenge is invalid")
    .max(191),
  code: v
    .string()
    .trim()
    .required("Enter the one-time code")
    .pattern(/^\d{6}$/, "Enter the six-digit one-time code"),
  returnTo: v.string().max(2048).optional(),
});

export const otpIssueSchema = v.object({
  method: v
    .string()
    .required("Choose an OTP delivery method")
    .oneOf(["email-otp", "sms-otp"], "Choose email or SMS OTP"),
  destination: v.string().trim().max(320).optional(),
});

export const otpSchema = v.object({
  challengeId: v
    .string()
    .required("OTP challenge is missing")
    .min(8, "OTP challenge is invalid")
    .max(191),
  code: v
    .string()
    .trim()
    .required("Enter the one-time code")
    .pattern(/^\d{6}$/, "Enter the six-digit one-time code"),
});

export const mfaOtpRequestSchema = v.object({
  mfaToken: v
    .string()
    .required("MFA transaction is missing")
    .min(20, "MFA transaction is invalid")
    .max(512),
  method: v
    .string()
    .required("Choose an MFA delivery method")
    .oneOf(["email-otp", "sms-otp"], "Choose email or SMS OTP"),
});

export const mfaSchema = v.object({
  mfaToken: v
    .string()
    .required("MFA transaction is missing")
    .min(20, "MFA transaction is invalid")
    .max(512),
  method: v
    .string()
    .required("Choose a verification method")
    .oneOf(
      ["totp", "recovery-code", "email-otp", "sms-otp"],
      "Choose a supported verification method",
    ),
  challengeId: v.string().max(191).optional(),
  code: v
    .string()
    .trim()
    .required("Enter the verification code")
    .min(6, "Verification code is too short")
    .max(32),
  returnTo: v.string().max(2048).optional(),
});

export const sessionRevokeSchema = v.object({
  sessionId: v.string().required("Session ID is missing").min(3).max(191),
});

export const impersonationStartSchema = v.object({
  targetUserId: v.string().required("Choose a user to impersonate").min(3).max(191),
  reason: v.string().trim().max(500).optional(),
});

export const passkeyRegistrationOptionsSchema = v.object({
  rpId: v.string().trim().max(253).optional(),
  rpName: v.string().trim().max(120).optional(),
  origin: v.string().trim().url("Enter a valid origin").optional(),
});

export const passkeyRegistrationVerifySchema = v.object({
  key: v.string().required("Passkey challenge key is missing").min(8).max(512),
  response: v.unknown().required("Passkey response is missing"),
  rpId: v.string().trim().max(253).optional(),
  origin: v.string().trim().url("Enter a valid origin").optional(),
  name: v.string().trim().max(120).optional(),
});

export const passkeyAuthenticationOptionsSchema = v.object({
  identifier: v.string().trim().max(320).optional(),
  rpId: v.string().trim().max(253).optional(),
  origin: v.string().trim().url("Enter a valid origin").optional(),
});

export const passkeyAuthenticationVerifySchema = v.object({
  key: v.string().required("Passkey challenge key is missing").min(8).max(512),
  response: v.unknown().required("Passkey response is missing"),
  rpId: v.string().trim().max(253).optional(),
  origin: v.string().trim().url("Enter a valid origin").optional(),
});

export const authenticatorSetupSchema = v.object({
  label: v.string().trim().max(120).optional(),
});

export const authenticatorConfirmSchema = v.object({
  credentialId: v.string().required("Authenticator credential is missing").min(3).max(191),
  code: v
    .string()
    .trim()
    .required("Enter the authenticator code")
    .pattern(/^\d{6}$/, "Enter the six-digit authenticator code"),
});

export const authenticatorDisableSchema = v.object({
  credentialId: v.string().required("Authenticator credential is missing").min(3).max(191),
});

export const recoveryCodesSchema = v.object({
  count: v.number().integer("Recovery code count must be a whole number").min(1).max(50).optional(),
});

export const emptyActionSchema = v.object({});

export const changePasswordSchema = v.object({
  currentPassword: v.string().required("Enter your current password").max(256),
  nextPassword: strongPassword(),
});

export interface AuthSchemaSet {
  register: ObjectSchema;
  signUp: ObjectSchema;
  login: ObjectSchema;
  verificationRequest: ObjectSchema;
  verificationToken: ObjectSchema;
  passwordResetRequest: ObjectSchema;
  passwordReset: ObjectSchema;
  invitationAccept: ObjectSchema;
  magicLinkRequest: ObjectSchema;
  magicLinkConsume: ObjectSchema;
  otpLoginRequest: ObjectSchema;
  otpLoginComplete: ObjectSchema;
  otpIssue: ObjectSchema;
  otpVerify: ObjectSchema;
  mfaOtpRequest: ObjectSchema;
  mfaComplete: ObjectSchema;
  sessionRevoke: ObjectSchema;
  impersonationStart: ObjectSchema;
  passkeyRegistrationOptions: ObjectSchema;
  passkeyRegistrationVerify: ObjectSchema;
  passkeyAuthenticationOptions: ObjectSchema;
  passkeyAuthenticationVerify: ObjectSchema;
  authenticatorSetup: ObjectSchema;
  authenticatorConfirm: ObjectSchema;
  authenticatorDisable: ObjectSchema;
  recoveryCodes: ObjectSchema;
  emptyAction: ObjectSchema;
  changePassword: ObjectSchema;
}

export type AuthSchemaOverrides = Partial<AuthSchemaSet>;

export const authSchemas: AuthSchemaSet = {
  register: signUpSchema,
  signUp: signUpSchema,
  login: loginSchema,
  verificationRequest: verificationRequestSchema,
  verificationToken: verificationTokenSchema,
  passwordResetRequest: passwordResetRequestSchema,
  passwordReset: passwordResetSchema,
  invitationAccept: invitationAcceptSchema,
  magicLinkRequest: magicLinkRequestSchema,
  magicLinkConsume: magicLinkConsumeSchema,
  otpLoginRequest: otpLoginRequestSchema,
  otpLoginComplete: otpLoginCompleteSchema,
  otpIssue: otpIssueSchema,
  otpVerify: otpSchema,
  mfaOtpRequest: mfaOtpRequestSchema,
  mfaComplete: mfaSchema,
  sessionRevoke: sessionRevokeSchema,
  impersonationStart: impersonationStartSchema,
  passkeyRegistrationOptions: passkeyRegistrationOptionsSchema,
  passkeyRegistrationVerify: passkeyRegistrationVerifySchema,
  passkeyAuthenticationOptions: passkeyAuthenticationOptionsSchema,
  passkeyAuthenticationVerify: passkeyAuthenticationVerifySchema,
  authenticatorSetup: authenticatorSetupSchema,
  authenticatorConfirm: authenticatorConfirmSchema,
  authenticatorDisable: authenticatorDisableSchema,
  recoveryCodes: recoveryCodesSchema,
  emptyAction: emptyActionSchema,
  changePassword: changePasswordSchema,
};

export function resolveAuthSchemas(overrides: AuthSchemaOverrides = {}): AuthSchemaSet {
  return { ...authSchemas, ...overrides };
}

export const authBrowserSchemaMap = {
  "auth-register": "register",
  "auth-login": "login",
  "auth-verification-request": "verificationRequest",
  "auth-verification-token": "verificationToken",
  "auth-password-request": "passwordResetRequest",
  "auth-password-reset": "passwordReset",
  "auth-invitation": "invitationAccept",
  "auth-magic-link-request": "magicLinkRequest",
  "auth-magic-link-consume": "magicLinkConsume",
  "auth-otp-login-request": "otpLoginRequest",
  "auth-otp-login-complete": "otpLoginComplete",
  "auth-otp-issue": "otpIssue",
  "auth-otp": "otpVerify",
  "auth-mfa-otp-request": "mfaOtpRequest",
  "auth-mfa": "mfaComplete",
  "auth-session-revoke": "sessionRevoke",
  "auth-impersonation-start": "impersonationStart",
  "auth-passkey-registration-options": "passkeyRegistrationOptions",
  "auth-passkey-registration-verify": "passkeyRegistrationVerify",
  "auth-passkey-authentication-options": "passkeyAuthenticationOptions",
  "auth-passkey-authentication-verify": "passkeyAuthenticationVerify",
  "auth-authenticator-setup": "authenticatorSetup",
  "auth-authenticator-confirm": "authenticatorConfirm",
  "auth-authenticator-disable": "authenticatorDisable",
  "auth-recovery-codes": "recoveryCodes",
  "auth-empty": "emptyAction",
  "auth-change-password": "changePassword",
} as const satisfies Record<string, keyof AuthSchemaSet>;

export function authBrowserSchemaDescriptors(
  schemas: AuthSchemaSet = authSchemas,
): Record<string, SchemaDescriptor> {
  const descriptors: Record<string, SchemaDescriptor> = {};
  for (const [browserName, schemaName] of Object.entries(authBrowserSchemaMap)) {
    descriptors[browserName] = schemas[schemaName].describe();
  }
  return descriptors;
}
