
import {
  asArray,
  asBoolean,
  asCodec,
  asDate,
  asEither,
  asNumber,
  asObject,
  asOptional,
  asString,
  asUnknown,
  asValue,
  uncleaner
} from 'cleaners'
import { base16, base32, base64 } from 'rfc4648'

































/**
 * A string of hex-encoded binary data.
 */
export const asBase16 = asCodec(
  raw => base16.parse(asString(raw)),
  clean => base16.stringify(clean).toLowerCase()
)

/**
 * A string of base32-encoded binary data.
 */
export const asBase32 = asCodec(
  raw => base32.parse(asString(raw), { loose: true }),
  clean => base32.stringify(clean, { pad: false })
)

/**
 * A string of base64-encoded binary data.
 */
export const asBase64 = asCodec(
  raw => base64.parse(asString(raw)),
  clean => base64.stringify(clean)
)

// ---------------------------------------------------------------------
// public Edge types
// ---------------------------------------------------------------------

export const asEdgePendingVoucher = asObject({
  voucherId: asString,
  activates: asDate,
  created: asDate,
  ip: asString,
  ipDescription: asString,
  deviceDescription: asOptional(asString)
})

// ---------------------------------------------------------------------
// internal Edge types
// ---------------------------------------------------------------------

export const asEdgeBox = asObject({
  encryptionType: asNumber,
  data_base64: asBase64,
  iv_hex: asBase16
})

export const asEdgeKeyBox = asObject({
  created: asOptional(asDate),
  data_base64: asBase64,
  encryptionType: asNumber,
  iv_hex: asBase16
})

export const asEdgeSnrp = asObject({
  salt_hex: asBase16,
  n: asNumber,
  r: asNumber,
  p: asNumber
})

export const asEdgeLobbyRequest = asObject({
  loginRequest: asOptional(asObject({ appId: asString }).withRest),
  publicKey: asBase64,
  timeout: asOptional(asNumber)
}).withRest

export const asEdgeLobbyReply = asObject({
  publicKey: asBase64,
  box: asEdgeBox
})

/**
 * An array of base64-encoded hashed recovery answers.
 */
export const asRecovery2Auth = asArray(asBase64)

// ---------------------------------------------------------------------
// top-level request & response bodies
// ---------------------------------------------------------------------

export const asLoginRequestBody = asObject({
  // The request payload:
  data: asUnknown,

  // Common fields for all login methods:
  challengeId: asOptional(asString),
  deviceDescription: asOptional(asString),
  otp: asOptional(asString),
  syncToken: asOptional(asString),
  voucherId: asOptional(asString),
  voucherAuth: asOptional(asBase64),

  // Secret-key login:
  loginId: asOptional(asBase64),
  loginAuth: asOptional(asBase64),

  // Password login:
  userId: asOptional(asBase64),
  passwordAuth: asOptional(asBase64),

  // PIN login:
  pin2Id: asOptional(asBase64),
  pin2Auth: asOptional(asBase64),

  // Recovery login:
  recovery2Id: asOptional(asBase64),
  recovery2Auth: asOptional(asRecovery2Auth),

  // Messages:
  loginIds: asOptional(asArray(asBase64)),

  // OTP reset:
  otpResetAuth: asOptional(asString),

  // Legacy:
  did: asOptional(asString),
  l1: asOptional(asBase64),
  lp1: asOptional(asBase64),
  lpin1: asOptional(asBase64),
  lra1: asOptional(asBase64),
  recoveryAuth: asOptional(asBase64) // lra1
})

export const asLoginResponseBody = asObject({
  // The response payload:
  results: asOptional(asUnknown),

  // What type of response is this (success or failure)?:
  status_code: asNumber,
  message: asString
})

// ---------------------------------------------------------------------
// request payloads
// ---------------------------------------------------------------------

export const asChangeOtpPayload = asObject({
  otpTimeout: asOptional(asNumber, 7 * 24 * 60 * 60), // seconds
  otpKey: asBase32
})

export const asChangePasswordPayload = asObject(
  {
    passwordAuth: asBase64,
    passwordAuthBox: asEdgeBox,
    passwordAuthSnrp: asEdgeSnrp,
    passwordBox: asEdgeBox,
    passwordKeySnrp: asEdgeSnrp
  }
)

export const asChangePin2IdPayload = asObject({
  pin2Id: asBase64
})

export const asChangePin2Payload = asObject({
  pin2Id: asOptional(asBase64),
  pin2Auth: asOptional(asBase64),
  pin2Box: asOptional(asEdgeBox),
  pin2KeyBox: asOptional(asEdgeBox),
  pin2TextBox: asEdgeBox
})

export const asChangeRecovery2IdPayload =
  asObject({
    recovery2Id: asBase64
  })

export const asChangeRecovery2Payload =
  asObject({
    recovery2Id: asBase64,
    recovery2Auth: asRecovery2Auth,
    recovery2Box: asEdgeBox,
    recovery2KeyBox: asEdgeBox,
    question2Box: asEdgeBox
  })

export const asChangeSecretPayload = asObject({
  loginAuthBox: asEdgeBox,
  loginAuth: asBase64
})

export const asChangeUsernamePayload = asObject(
  {
    userId: asBase64,
    userTextBox: asEdgeBox
  }
)

export const asChangeVouchersPayload = asObject(
  {
    approvedVouchers: asOptional(asArray(asString)),
    rejectedVouchers: asOptional(asArray(asString))
  }
)

export const asCreateKeysPayload = asObject({
  keyBoxes: asArray(asEdgeBox),
  newSyncKeys: asOptional(asArray(asString), () => [])
})

export const asCreateLoginPayload = asObject({
  appId: asString,
  loginId: asBase64,
  parentBox: asOptional(asEdgeBox)
})

// ---------------------------------------------------------------------
// response payloads
// ---------------------------------------------------------------------

export const asChallengeErrorPayload = asObject(
  {
    challengeId: asString,
    challengeUri: asString
  }
)

export const asCreateChallengePayload =
  asObject({
    challengeId: asString,
    challengeUri: asOptional(asString)
  })

export const asLobbyPayload = asObject({
  request: asEdgeLobbyRequest,
  replies: asArray(asEdgeLobbyReply)
})

const asTrue = asValue(true)

export const asLoginPayload = asObject({
  // Identity:
  appId: asString,
  created: asDate,
  loginId: asBase64,
  syncToken: asOptional(asString),

  // Nested logins:
  children: asOptional(asArray(raw => asLoginPayload(raw))),
  parentBox: asOptional(asEdgeBox),

  // 2-factor login:
  otpKey: asOptional(asEither(asTrue, asBase32)),
  otpResetDate: asOptional(asDate),
  otpTimeout: asOptional(asNumber),

  // Password login:
  passwordAuthBox: asOptional(asEdgeBox),
  passwordAuthSnrp: asOptional(asEdgeSnrp),
  passwordBox: asOptional(asEither(asTrue, asEdgeBox)),
  passwordKeySnrp: asOptional(asEdgeSnrp),

  // PIN v2 login:
  pin2Box: asOptional(asEither(asTrue, asEdgeBox)),
  pin2KeyBox: asOptional(asEdgeBox),
  pin2TextBox: asOptional(asEdgeBox),

  // Recovery v2 login:
  question2Box: asOptional(asEdgeBox),
  recovery2Box: asOptional(asEither(asTrue, asEdgeBox)),
  recovery2KeyBox: asOptional(asEdgeBox),

  // Secret-key login:
  loginAuthBox: asOptional(asEdgeBox),

  // Username:
  userId: asOptional(asBase64),
  userTextBox: asOptional(asEdgeBox),

  // Voucher login:
  pendingVouchers: asOptional(asArray(asEdgePendingVoucher), () => []),

  // Resources:
  keyBoxes: asOptional(asArray(asEdgeKeyBox)),
  mnemonicBox: asOptional(asEdgeBox),
  rootKeyBox: asOptional(asEdgeBox),
  syncKeyBox: asOptional(asEdgeBox)
})

export const asMessagesPayload = asArray(
  asObject({
    loginId: asBase64,
    otpResetPending: asOptional(asBoolean, false),
    pendingVouchers: asOptional(asArray(asEdgePendingVoucher), () => []),
    recovery2Corrupt: asOptional(asBoolean, false)
  })
)

export const asOtpErrorPayload = asObject({
  login_id: asOptional(asBase64),
  otp_reset_auth: asOptional(asString),
  otp_timeout_date: asOptional(asDate),
  reason: asOptional(asValue('ip', 'otp'), 'otp'),
  voucher_activates: asOptional(asDate),
  voucher_auth: asOptional(asBase64),
  voucher_id: asOptional(asString)
})

export const asOtpResetPayload = asObject({
  otpResetDate: asDate
})

export const asPasswordErrorPayload = asObject({
  wait_seconds: asOptional(asNumber)
})

export const asRecovery2InfoPayload = asObject({
  question2Box: asEdgeBox
})

export const asUsernameInfoPayload = asObject({
  loginId: asBase64,

  // Password login:
  passwordAuthSnrp: asOptional(asEdgeSnrp),

  // Recovery v1 login:
  questionBox: asOptional(asEdgeBox),
  questionKeySnrp: asOptional(asEdgeSnrp),
  recoveryAuthSnrp: asOptional(asEdgeSnrp)
})

// ---------------------------------------------------------------------
// uncleaners
// ---------------------------------------------------------------------

// Common types:
export const wasEdgeBox = uncleaner(asEdgeBox)
export const wasEdgeLobbyReply = uncleaner(asEdgeLobbyReply)
export const wasEdgeLobbyRequest =
  uncleaner(asEdgeLobbyRequest)

// Top-level request / response bodies:
export const wasLoginRequestBody =
  uncleaner(asLoginRequestBody)
export const wasLoginResponseBody =
  uncleaner(asLoginResponseBody)

// Request payloads:
export const wasChangeOtpPayload =
  uncleaner(asChangeOtpPayload)
export const wasChangePasswordPayload = uncleaner(
  asChangePasswordPayload
)
export const wasChangePin2IdPayload = uncleaner(
  asChangePin2IdPayload
)
export const wasChangePin2Payload =
  uncleaner(asChangePin2Payload)
export const wasChangeRecovery2IdPayload = uncleaner(
  asChangeRecovery2IdPayload
)
export const wasChangeRecovery2Payload = uncleaner(
  asChangeRecovery2Payload
)
export const wasChangeSecretPayload = uncleaner(
  asChangeSecretPayload
)
export const wasChangeUsernamePayload = uncleaner(
  asChangeUsernamePayload
)
export const wasChangeVouchersPayload = uncleaner(
  asChangeVouchersPayload
)
export const wasCreateKeysPayload =
  uncleaner(asCreateKeysPayload)
export const wasCreateLoginPayload =
  uncleaner(asCreateLoginPayload)

// Response payloads:
export const wasChallengeErrorPayload = uncleaner(
  asChallengeErrorPayload
)
export const wasCreateChallengePayload = uncleaner(
  asCreateChallengePayload
)
export const wasLobbyPayload = uncleaner(asLobbyPayload)
export const wasLoginPayload = uncleaner(asLoginPayload)
export const wasMessagesPayload = uncleaner(asMessagesPayload)
export const wasOtpErrorPayload = uncleaner(asOtpErrorPayload)
export const wasOtpResetPayload = uncleaner(asOtpResetPayload)
export const wasPasswordErrorPayload = uncleaner(
  asPasswordErrorPayload
)
export const wasRecovery2InfoPayload = uncleaner(
  asRecovery2InfoPayload
)
export const wasUsernameInfoPayload = uncleaner(
  asUsernameInfoPayload
)
