import { createHttpError } from '../../utils/http.js'
import { parseTaskContext } from '../../utils/task-json.js'
import { isTaskFinalStatus, normalizeTaskStatus, TASK_STATUS } from '../../domain/task-status.js'
import type {
  ClaimIdentity,
  ClaimIdentityAdminSummary,
  ClaimIdentityPayload,
} from '../../types/claim-identity.js'
import type { TaskRow } from '../../types/repository/rows.js'
import type { JsonObject } from '../../types/json.js'

export type {
  ClaimIdentity,
  ClaimIdentityAdminSummary,
  ClaimIdentityPayload,
} from '../../types/claim-identity.js'

// 游戏 UID（角色编号）长度上限：91 单号为 16 位纯数字，防止用户把 91 单号误当 UID 提交。
const CLAIM_UID_MAX_LENGTH = 15

export function normalizeClaimUid(value: unknown): string {
  return String(value || '')
    .trim()
    .replace(/\s+/g, '')
}

export function assertValidClaimUid(value: unknown): string {
  const uid = normalizeClaimUid(value)
  if (!uid) {
    throw createHttpError('请输入游戏 UID', {
      statusCode: 400,
      errorCode: 'claim_uid_required',
    })
  }

  if (uid.length > CLAIM_UID_MAX_LENGTH) {
    throw createHttpError(`UID 长度不能超过 ${CLAIM_UID_MAX_LENGTH} 个字符`, {
      statusCode: 400,
      errorCode: 'claim_uid_too_long',
    })
  }

  if (!/^[0-9]+$/.test(uid)) {
    throw createHttpError('UID 仅支持纯数字', {
      statusCode: 400,
      errorCode: 'claim_uid_invalid',
    })
  }

  return uid
}

export function normalizeClaimIdentity(value: unknown): ClaimIdentity {
  const source = isPlainObject(value) ? value : {}
  return {
    expectedUid: normalizeClaimUid(source.expectedUid || source.uid),
    submittedAt: source.submittedAt ? String(source.submittedAt) : null,
    source: String(source.source || '').trim(),
  }
}

export function getClaimIdentityFromContext(context: unknown): ClaimIdentity {
  const source = isPlainObject(context) ? context : {}
  return normalizeClaimIdentity(source.claimIdentity)
}

export function getClaimIdentityFromTask(task: Partial<TaskRow> | null | undefined): ClaimIdentity {
  return getClaimIdentityFromContext(parseTaskContext(task))
}

export function hasClaimExpectedUid(taskOrContext: unknown): boolean {
  if (
    taskOrContext &&
    typeof taskOrContext === 'object' &&
    'context_json' in (taskOrContext as object)
  ) {
    return Boolean(getClaimIdentityFromTask(taskOrContext as TaskRow).expectedUid)
  }
  return Boolean(getClaimIdentityFromContext(taskOrContext).expectedUid)
}

export function isClaimUidMatched(expectedUid: unknown, boundUid: unknown): boolean {
  const expected = normalizeClaimUid(expectedUid)
  const bound = normalizeClaimUid(boundUid)
  if (!expected || !bound) {
    return false
  }
  return expected === bound
}

export function resolveBoundRoleUid(flowLike: unknown): string {
  const flow = isPlainObject(flowLike) ? flowLike : {}
  const binding = isPlainObject(flow.binding) ? flow.binding : {}
  const role = isPlainObject(flow.role) ? flow.role : {}
  return normalizeClaimUid(
    binding.roleId || role.rid || role.roleId || binding.uid || role.uid || '',
  )
}

export function assertClaimExpectedUidReady(task: Partial<TaskRow> | null | undefined): string {
  const expectedUid = getClaimIdentityFromTask(task).expectedUid
  if (!expectedUid) {
    throw createHttpError('请先通过领取页填写游戏 UID（旧单无 UID 不可自动履约）', {
      statusCode: 409,
      errorCode: 'claim_uid_not_submitted',
    })
  }
  return expectedUid
}

/**
 * lewan 自动发货前：必须已有 expectedUid（不再回落「仅确认角色」旧路径）。
 * mock 任务可跳过。
 */
export function assertLewanAutoFulfillmentUidReady(
  task: Partial<TaskRow> | null | undefined,
  options: { allowMockSkip?: boolean; errorCodePrefix?: string } = {},
): string {
  const context = parseTaskContext(task)
  const flow =
    context.kuaishouCloudFulfillment && typeof context.kuaishouCloudFulfillment === 'object'
      ? (context.kuaishouCloudFulfillment as JsonObject)
      : {}
  const mock = flow.mock && typeof flow.mock === 'object' ? (flow.mock as JsonObject) : null
  if (options.allowMockSkip !== false && mock?.enabled === true) {
    return getClaimIdentityFromTask(task).expectedUid
  }

  const expectedUid = getClaimIdentityFromTask(task).expectedUid
  if (!expectedUid) {
    const prefix = String(options.errorCodePrefix || 'kuaishou_cloud').trim() || 'kuaishou_cloud'
    throw createHttpError(
      '该 lewan 任务尚未填写游戏 UID，请用户打开领取页提交 UID 后再发货（旧单不再支持无 UID 自动发货）',
      {
        statusCode: 409,
        errorCode: `${prefix}_expected_uid_required`,
      },
    )
  }
  return expectedUid
}

export function buildClaimIdentityAdminSummary(
  context: unknown,
  options: { flowLike?: unknown; taskRoleId?: unknown; taskRoleName?: unknown } = {},
): ClaimIdentityAdminSummary {
  const identity = getClaimIdentityFromContext(context)
  const flow = isPlainObject(options.flowLike) ? options.flowLike : {}
  const binding = isPlainObject(flow.binding) ? flow.binding : {}
  const role = isPlainObject(flow.role) ? flow.role : {}
  const shipped = isPlainObject(flow.shipped) ? flow.shipped : null
  const dispatch = isPlainObject(flow.dispatch) ? flow.dispatch : {}
  const shippedUid = normalizeClaimUid(shipped?.roleId || shipped?.expectedUid || '')
  const shippedRoleName = String(shipped?.roleName || '').trim()
  const shippedAt = shipped?.dispatchedAt ? String(shipped.dispatchedAt) : null
  const dispatchSucceeded = String(dispatch.status || '').trim() === 'success'
  // 已发货优先用快照，避免 claim 轮询换号后误报 UID 不一致
  const useShipped = Boolean(shippedUid && (dispatchSucceeded || shippedAt))
  const liveBoundUid =
    resolveBoundRoleUid(options.flowLike) || normalizeClaimUid(options.taskRoleId)
  const liveBoundRoleName = String(
    role.name || binding.roleName || options.taskRoleName || '',
  ).trim()
  const boundUid = useShipped ? shippedUid : liveBoundUid
  const boundRoleName = useShipped ? shippedRoleName || liveBoundRoleName : liveBoundRoleName
  const ready = Boolean(identity.expectedUid)
  const uidMatched = ready && boundUid ? isClaimUidMatched(identity.expectedUid, boundUid) : null
  const liveMismatched =
    useShipped && liveBoundUid && shippedUid && !isClaimUidMatched(shippedUid, liveBoundUid)

  let note = '旧单或未提交 UID：用户须先打开领取页填写 UID，否则禁止自动发货'
  if (ready) {
    if (useShipped) {
      if (uidMatched === true) {
        note = liveMismatched
          ? `已发货：道具发至 UID ${shippedUid}${shippedRoleName ? `（${shippedRoleName}）` : ''}；当前虚拟号角色已变化，请勿以当前绑定为准`
          : `已发货：道具发至 UID ${shippedUid}${shippedRoleName ? `（${shippedRoleName}）` : ''}，与填写 UID 一致`
      } else if (uidMatched === false) {
        note = `已发货快照角色 ID（${boundUid}）与填写 UID（${identity.expectedUid}）不一致，请核对事件日志`
      } else {
        note = '已发货，但快照缺少角色 ID'
      }
    } else if (dispatchSucceeded) {
      note =
        uidMatched === true
          ? '已发货：当前绑定角色与填写 UID 一致（无发货快照，建议以事件日志为准）'
          : uidMatched === false
            ? '已发货，但当前绑定角色与填写 UID 不一致（可能为发货后换号污染；请核对 dispatch 事件中的 roleId）'
            : '已发货，等待核对绑定角色'
    } else if (uidMatched === true) {
      note = 'UID 已匹配，可继续履约'
    } else if (uidMatched === false) {
      note = '绑定角色 ID 与填写 UID 不一致'
    } else {
      note = '已填 UID，等待绑定识别'
    }
  }

  return {
    expectedUid: identity.expectedUid,
    submittedAt: identity.submittedAt,
    source: identity.source,
    ready,
    boundUid,
    boundRoleName,
    uidMatched,
    compatibilityMode: ready ? ('uid' as const) : ('legacy_no_uid' as const),
    note,
    displaySource: useShipped ? 'shipped' : 'live',
    shippedUid,
    shippedRoleName,
    shippedAt,
  }
}

export function assertBoundUidMatchesExpected(
  task: Partial<TaskRow> | null | undefined,
  flowLike: unknown,
  options: { errorCodePrefix?: string } = {},
): string {
  const expectedUid = assertClaimExpectedUidReady(task)
  const boundUid = resolveBoundRoleUid(flowLike)
  const prefix = String(options.errorCodePrefix || 'claim').trim() || 'claim'

  if (!boundUid) {
    throw createHttpError('尚未识别到绑定角色 ID，请完成绑定后刷新再试', {
      statusCode: 409,
      errorCode: `${prefix}_bound_uid_missing`,
    })
  }

  if (!isClaimUidMatched(expectedUid, boundUid)) {
    throw createHttpError(
      `绑定角色 ID（${boundUid}）与填写的 UID（${expectedUid}）不一致，请重新绑定正确角色`,
      {
        statusCode: 409,
        errorCode: `${prefix}_uid_mismatch`,
      },
    )
  }

  return expectedUid
}

export function canUpdateClaimUid(task: Partial<TaskRow> | null | undefined): boolean {
  const status = normalizeTaskStatus(task?.task_status)
  if (isTaskFinalStatus(status)) {
    return false
  }

  if (
    status === TASK_STATUS.REDEEMING ||
    status === TASK_STATUS.DISPATCHED_PENDING_RETURN ||
    status === TASK_STATUS.COMPLETED ||
    status === TASK_STATUS.REDEEMED
  ) {
    return false
  }

  const context = parseTaskContext(task)
  const flow = isPlainObject(context.kuaishouCloudFulfillment)
    ? context.kuaishouCloudFulfillment
    : {}
  const dispatch = isPlainObject(flow.dispatch) ? flow.dispatch : {}
  if (String(dispatch.status || '').trim() === 'success') {
    return false
  }

  const feifei = isPlainObject(context.kuaishouFeifei) ? context.kuaishouFeifei : {}
  const rechargeStatus = Number(feifei.rechargeStatus || 0) || 0
  if (rechargeStatus === 30) {
    return false
  }

  return true
}

/**
 * 将 uid 拼到目标 H5 链接 query 中（覆盖已有 uid 参数）。
 */
export function appendUidToUrl(targetUrl: unknown, uid: unknown): string {
  const rawUrl = String(targetUrl || '').trim()
  const normalizedUid = normalizeClaimUid(uid)
  if (!rawUrl || !normalizedUid) {
    return rawUrl
  }

  try {
    const url = new URL(rawUrl)
    url.searchParams.set('uid', normalizedUid)
    return url.toString()
  } catch {
    const separator = rawUrl.includes('?') ? '&' : '?'
    if (/([?&])uid=/i.test(rawUrl)) {
      return rawUrl.replace(/([?&])uid=[^&]*/i, `$1uid=${encodeURIComponent(normalizedUid)}`)
    }
    return `${rawUrl}${separator}uid=${encodeURIComponent(normalizedUid)}`
  }
}

export function buildClaimIdentityPayload(value: unknown): ClaimIdentityPayload {
  const identity = normalizeClaimIdentity(value)
  return {
    expectedUid: identity.expectedUid,
    submittedAt: identity.submittedAt,
    source: identity.source,
    ready: Boolean(identity.expectedUid),
  }
}

function isPlainObject(value: unknown): value is JsonObject {
  return Object.prototype.toString.call(value) === '[object Object]'
}
