import { createTaskEvent } from '../../../repositories/task-event-repo.js'
import { updateTask } from '../../../repositories/task-repo.js'
import { buildClaimUrl } from '../../claim/claim-service.js'
import {
  backCloudtentaclesVirtualNumber,
  getCloudtentaclesBindUrl,
} from '../../platforms/cloudtentacles/virtual-number-service.js'
import { createHttpError } from '../../../utils/http.js'
import { nowIso } from '../../../utils/time.js'
import { TASK_STATUS, normalizeTaskStatus } from '../../../domain/task-status.js'
import {
  isKuaishouCloudTask,
  maskPhone,
  normalizeKuaishouCloudFlow,
  resolveKuaishouCloudBindUrlExpiresAt,
  type JsonObject,
} from './domain.js'
import {
  prepareKuaishouCloudBindResourceWithFallback,
  resolveKuaishouCloudVnKeyCandidates,
} from './binding-resources.js'
import { resolvePersistedCloudtentaclesContextBySourceKeys } from './cloudtentacles-context.js'
import {
  isCloudtentaclesPermissionLikeError,
  wrapCloudtentaclesOperationError,
} from './cloudtentacles-errors.js'
import { getTaskClaimExpiresAt, normalizeActor, parseTaskContext } from './task-context.js'
import { ensureTaskClaimLink } from './ensure-claim-link.js'
import {
  buildPendingRoleWithDefaultSnapshot,
  captureKuaishouCloudDefaultRoleSnapshot,
} from './role-state.js'
import type { TaskRow } from '../../../types/repository/rows.js'

/**
 * 换绑角色：尽量退旧号后取新号+新绑链。
 * - 不涉及订单退款；claimUrl 复用
 * - 旧号退还失败时默认软跳过继续取新号（避免卡死），并在事件/文案中标明需人工清理旧号
 * - 也可先尝试同 vn 重取 bindUrl（reuseExistingVn）
 */
export async function rebindKuaishouCloudTaskRole(task: TaskRow, options: JsonObject = {}) {
  if (!isKuaishouCloudTask(task)) {
    throw createHttpError('当前任务不是 kuaishou-lewan 履约任务', {
      statusCode: 409,
      errorCode: 'kuaishou_cloud_task_invalid',
    })
  }

  const normalizedStatus = normalizeTaskStatus(task.task_status)
  const rebindAllowedStatuses = new Set<string>([
    TASK_STATUS.WAITING_BINDING,
    TASK_STATUS.ROLE_CONFIRMED,
    TASK_STATUS.MANUAL_REVIEW,
    TASK_STATUS.RETRY_PENDING,
  ])
  if (!rebindAllowedStatuses.has(normalizedStatus)) {
    throw createHttpError('当前任务状态不可换绑角色', {
      statusCode: 409,
      errorCode: 'kuaishou_cloud_rebind_not_allowed',
    })
  }

  const now = nowIso()
  const actor = normalizeActor(options.actor)
  const taskContext = parseTaskContext(task)
  const flow = normalizeKuaishouCloudFlow(taskContext.kuaishouCloudFulfillment)

  if (
    flow.dispatch.status === 'success' ||
    normalizeTaskStatus(task.task_status) === TASK_STATUS.DISPATCHED_PENDING_RETURN
  ) {
    throw createHttpError('当前任务已经发货，不能换绑角色', {
      statusCode: 409,
      errorCode: 'kuaishou_cloud_rebind_after_dispatch_forbidden',
    })
  }

  if (!flow.binding.vnId || !flow.binding.vnKey) {
    throw createHttpError('当前任务缺少可退还的虚拟号信息，请先准备绑定资源', {
      statusCode: 409,
      errorCode: 'kuaishou_cloud_rebind_missing_binding_context',
    })
  }

  const source = String(options.source || 'system_rebind_role').trim() || 'system_rebind_role'
  // 优先 resolvedSourceKey，避免静默落到别的账号导致「权限不足」
  const preferredSourceKeys = [
    flow.binding.resolvedSourceKey,
    ...flow.binding.cloudSourceKeys,
  ].filter(Boolean)
  let cloudContext: JsonObject
  try {
    cloudContext = resolvePersistedCloudtentaclesContextBySourceKeys(preferredSourceKeys)
  } catch (error) {
    throw wrapCloudtentaclesOperationError(error, {
      action: 'resolve_source',
      actionLabel: '解析取号账号',
      sourceKey: flow.binding.resolvedSourceKey || flow.binding.cloudSourceKeys[0],
      vnKey: flow.binding.vnKey,
      vnId: flow.binding.vnId,
      vnPhone: flow.binding.vnPhone,
    })
  }

  const oldBinding = {
    vnKey: flow.binding.vnKey,
    vnId: flow.binding.vnId,
    vnPhone: flow.binding.vnPhone,
    bindUrl: flow.binding.bindUrl,
    bindPreparedAt: flow.binding.bindPreparedAt,
    bindExpiresAt: flow.binding.bindExpiresAt,
    roleName: flow.binding.roleName || flow.role.name || task.role_name || '',
    roleId: flow.binding.roleId || flow.role.rid || task.role_id || '',
    resolvedSourceKey:
      flow.binding.resolvedSourceKey || String(cloudContext.resolvedSourceKey || ''),
  }
  const previousRebind: JsonObject =
    flow.rebind && typeof flow.rebind === 'object' ? (flow.rebind as JsonObject) : {}
  const history = Array.isArray(previousRebind.history) ? previousRebind.history : []
  const attempt = Math.max(1, Number(previousRebind.currentAttempt || history.length || 0) + 1)
  const baseHistoryItem = {
    attempt,
    source,
    requestedAt: now,
    requestedBy: actor,
    oldBinding,
    cloudSourceKey: String(cloudContext.resolvedSourceKey || ''),
  }

  await createTaskEvent(
    task.id,
    'kuaishou_cloud_rebind_requested',
    {
      source,
      attempt,
      oldVnId: oldBinding.vnId,
      oldVnPhoneMasked: maskPhone(oldBinding.vnPhone),
      oldRoleName: oldBinding.roleName,
      oldRoleId: oldBinding.roleId,
      cloudSourceKey: baseHistoryItem.cloudSourceKey,
      actor,
    },
    now,
  )

  // 路径 A：仅重取同 vn 绑链（不退号、不换号）——适合链接过期但角色还要重绑同一会话时
  if (options.reuseExistingVn === true) {
    return rebindByRefreshingExistingVnBindUrl({
      task,
      taskContext,
      flow,
      cloudContext,
      oldBinding,
      previousRebind,
      history,
      baseHistoryItem,
      attempt,
      actor,
      source,
      now,
    })
  }

  // 标准换绑：先退旧号；旧号已被 CT 回收（权限不足等）→ 自动跳过并新取号出链
  // 唯一入口给客服用，不另开按钮
  const allowSkipBack = options.allowSkipBack !== false
  let oldNumberBackStatus: 'success' | 'skipped' | 'failed' = 'success'
  let oldNumberBackError = ''

  try {
    await backCloudtentaclesVirtualNumber({
      ...cloudContext,
      key: oldBinding.vnKey,
      id: oldBinding.vnId,
    })
    await createTaskEvent(
      task.id,
      'kuaishou_cloud_rebind_old_number_returned',
      {
        source,
        attempt,
        vnKey: oldBinding.vnKey,
        vnId: oldBinding.vnId,
        vnPhoneMasked: maskPhone(oldBinding.vnPhone),
        cloudSourceKey: baseHistoryItem.cloudSourceKey,
        actor,
      },
      now,
    )
  } catch (error) {
    oldNumberBackError = error instanceof Error ? error.message : String(error || '退号失败')
    const wrapped = wrapCloudtentaclesOperationError(error, {
      action: 'vn_back',
      actionLabel: '退还旧虚拟号',
      sourceKey: cloudContext.resolvedSourceKey || cloudContext.sourceKey,
      vnKey: oldBinding.vnKey,
      vnId: oldBinding.vnId,
      vnPhone: oldBinding.vnPhone,
    })

    await createTaskEvent(
      task.id,
      'kuaishou_cloud_rebind_old_number_back_failed',
      {
        source,
        attempt,
        vnKey: oldBinding.vnKey,
        vnId: oldBinding.vnId,
        vnPhoneMasked: maskPhone(oldBinding.vnPhone),
        cloudSourceKey: baseHistoryItem.cloudSourceKey,
        errorMessage: wrapped.message,
        permissionLike: isCloudtentaclesPermissionLikeError(error),
        actor,
      },
      now,
    )

    // 默认继续新取号（旧号被系统回收时最常见）；仅显式 allowSkipBack=false 才硬失败
    if (!allowSkipBack) {
      throw wrapped
    }

    oldNumberBackStatus = 'skipped'
  }

  let preparedBinding
  try {
    preparedBinding = await prepareKuaishouCloudBindResourceWithFallback({
      cloudContext,
      purpose: 'claim_rebind',
      vnKeyCandidates: resolveKuaishouCloudVnKeyCandidates({
        flow,
        binding: flow.binding,
      }),
    })
  } catch (error) {
    const errorMessage = error instanceof Error ? error.message : '新绑定资源准备失败'
    const failedContext = {
      ...taskContext,
      kuaishouCloudFulfillment: {
        ...flow,
        binding: {
          ...flow.binding,
          prepareStatus: 'pending',
          vnId: oldNumberBackStatus === 'success' ? 0 : flow.binding.vnId,
          vnPhone: oldNumberBackStatus === 'success' ? '' : flow.binding.vnPhone,
          bindUrl: '',
          bindPreparedAt: null,
          bindExpiresAt: null,
          bindProbeAt: now,
          bindProbeStatus: 'rebind_failed',
          bindProbeMessage: errorMessage,
          roleName: '',
          roleId: '',
        },
        role: {
          status: 'pending',
          name: '',
          rid: '',
          refreshedAt: now,
          errorMessage:
            oldNumberBackStatus === 'success'
              ? `旧虚拟号已退还，新绑定资源准备失败：${errorMessage}`
              : `旧号退还未成功且新号准备失败：${errorMessage}`,
          rawInfo: null,
        },
        rebind: {
          ...previousRebind,
          currentAttempt: attempt,
          history: [
            ...history,
            {
              ...baseHistoryItem,
              status: 'failed',
              oldNumberBackStatus,
              oldNumberBackError,
              errorMessage,
            },
          ],
        },
      },
    }
    const failedTask = await updateTask(task.id, {
      task_status: TASK_STATUS.PENDING_BINDING_PREPARE,
      role_id: '',
      role_name: '',
      role_confirmed_at: null,
      last_error:
        oldNumberBackStatus === 'success'
          ? `换绑失败，旧虚拟号已退还，新绑定资源准备失败：${errorMessage}`
          : `换绑失败：旧号退还失败（${oldNumberBackError}），且新号准备失败：${errorMessage}`,
      context_json: JSON.stringify(failedContext),
      updated_at: now,
    })

    await createTaskEvent(
      task.id,
      'kuaishou_cloud_rebind_failed',
      {
        source,
        attempt,
        errorMessage,
        oldNumberBackStatus,
        oldNumberBackError,
        actor,
      },
      now,
    )

    return {
      task: failedTask || task,
      claimUrl: buildClaimUrl(String(task.primary_claim_token || task.claim_token || '')),
      token: String(task.primary_claim_token || task.claim_token || ''),
      flow: normalizeKuaishouCloudFlow(
        parseTaskContext(failedTask || task).kuaishouCloudFulfillment,
      ),
    }
  }

  const claimLinkState = await ensureTaskClaimLink(task)
  const nextBindExpiresAt = resolveKuaishouCloudBindUrlExpiresAt(now)
  const defaultRoleSnapshot = await captureKuaishouCloudDefaultRoleSnapshot({
    cloudContext,
    preparedBinding,
    now,
  })
  const skipNote =
    oldNumberBackStatus === 'skipped'
      ? `（旧号 vnId=${oldBinding.vnId} 退还失败已跳过，账号=${baseHistoryItem.cloudSourceKey}，请稍后在 CT 人工清理）`
      : ''
  const nextContext = {
    ...taskContext,
    kuaishouCloudFulfillment: {
      ...flow,
      binding: {
        ...flow.binding,
        prepareStatus: 'ready',
        resolvedSourceKey: String(
          cloudContext.resolvedSourceKey || flow.binding.resolvedSourceKey || '',
        ).trim(),
        vnKey: preparedBinding.vnKey,
        vnId: preparedBinding.vnId,
        vnPhone: preparedBinding.vnPhone,
        bindUrl: preparedBinding.bindUrl,
        bindPreparedAt: now,
        bindExpiresAt: nextBindExpiresAt,
        bindProbeAt: null,
        bindProbeStatus: 'pending',
        bindProbeMessage: '',
        roleName: '',
        roleId: '',
      },
      role: buildPendingRoleWithDefaultSnapshot(defaultRoleSnapshot),
      rebind: {
        ...previousRebind,
        currentAttempt: attempt,
        history: [
          ...history,
          {
            ...baseHistoryItem,
            newBinding: {
              vnKey: preparedBinding.vnKey,
              vnId: preparedBinding.vnId,
              vnPhone: preparedBinding.vnPhone,
              bindUrl: preparedBinding.bindUrl,
              bindPreparedAt: now,
              bindExpiresAt: nextBindExpiresAt,
            },
            status: 'success',
            oldNumberBackStatus,
            oldNumberBackError,
            errorMessage: '',
          },
        ],
      },
    },
  }

  const updatedTask = await updateTask(task.id, {
    task_status: TASK_STATUS.WAITING_BINDING,
    user_action_status: 'pending_claim',
    claim_token: claimLinkState.token || task.claim_token || '',
    claim_expires_at: claimLinkState.expiredAt || getTaskClaimExpiresAt(task),
    role_id: '',
    role_name: '',
    role_confirmed_at: null,
    last_error: skipNote,
    context_json: JSON.stringify(nextContext),
    updated_at: now,
  })

  await createTaskEvent(
    task.id,
    'kuaishou_cloud_rebind_prepared',
    {
      source,
      attempt,
      oldVnId: oldBinding.vnId,
      oldVnPhoneMasked: maskPhone(oldBinding.vnPhone),
      vnKey: preparedBinding.vnKey,
      vnId: preparedBinding.vnId,
      vnPhoneMasked: maskPhone(preparedBinding.vnPhone),
      bindUrl: preparedBinding.bindUrl,
      oldNumberBackStatus,
      oldNumberBackError,
      cloudSourceKey: baseHistoryItem.cloudSourceKey,
      defaultRoleName: defaultRoleSnapshot.defaultName,
      defaultRoleId: defaultRoleSnapshot.defaultRid,
      defaultRoleCaptureStatus: defaultRoleSnapshot.defaultCaptureStatus,
      actor,
    },
    now,
  )

  return {
    task: updatedTask,
    claimUrl: claimLinkState.claimUrl,
    token: claimLinkState.token,
    flow: normalizeKuaishouCloudFlow(nextContext.kuaishouCloudFulfillment),
    oldNumberBackStatus,
  }
}

async function rebindByRefreshingExistingVnBindUrl(input: {
  task: TaskRow
  taskContext: JsonObject
  flow: ReturnType<typeof normalizeKuaishouCloudFlow>
  cloudContext: JsonObject
  oldBinding: JsonObject
  previousRebind: JsonObject
  history: unknown[]
  baseHistoryItem: JsonObject
  attempt: number
  actor: unknown
  source: string
  now: string
}) {
  const {
    task,
    taskContext,
    flow,
    cloudContext,
    oldBinding,
    previousRebind,
    history,
    baseHistoryItem,
    attempt,
    actor,
    source,
    now,
  } = input

  let bindUrl
  try {
    const bindUrlResult = await getCloudtentaclesBindUrl({
      ...cloudContext,
      key: oldBinding.vnKey,
      id: oldBinding.vnId,
    })
    bindUrl = String(bindUrlResult.bindUrl || '').trim()
    if (!bindUrl) {
      throw createHttpError('cloudtentacles 未返回绑定链接', {
        statusCode: 502,
        errorCode: 'kuaishou_cloud_empty_bind_url',
      })
    }
  } catch (error) {
    throw wrapCloudtentaclesOperationError(error, {
      action: 'vn_bind_url',
      actionLabel: '复用原虚拟号重取绑定链接',
      sourceKey: cloudContext.resolvedSourceKey || cloudContext.sourceKey,
      vnKey: oldBinding.vnKey,
      vnId: oldBinding.vnId,
      vnPhone: oldBinding.vnPhone,
    })
  }

  const claimLinkState = await ensureTaskClaimLink(task)
  const nextBindExpiresAt = resolveKuaishouCloudBindUrlExpiresAt(now)
  const nextContext = {
    ...taskContext,
    kuaishouCloudFulfillment: {
      ...flow,
      binding: {
        ...flow.binding,
        prepareStatus: 'ready',
        bindUrl,
        bindPreparedAt: now,
        bindExpiresAt: nextBindExpiresAt,
        bindProbeAt: null,
        bindProbeStatus: 'pending',
        bindProbeMessage: '',
        // 清空角色，要求用户用正确 UID 重新绑定
        roleName: '',
        roleId: '',
      },
      role: {
        ...flow.role,
        status: 'pending',
        name: '',
        rid: '',
        refreshedAt: now,
        errorMessage: '',
        rawInfo: null,
      },
      rebind: {
        ...previousRebind,
        currentAttempt: attempt,
        history: [
          ...history,
          {
            ...baseHistoryItem,
            status: 'success',
            mode: 'reuse_existing_vn',
            newBinding: {
              vnKey: oldBinding.vnKey,
              vnId: oldBinding.vnId,
              vnPhone: oldBinding.vnPhone,
              bindUrl,
              bindPreparedAt: now,
              bindExpiresAt: nextBindExpiresAt,
            },
            errorMessage: '',
          },
        ],
      },
    },
  }

  const updatedTask = await updateTask(task.id, {
    task_status: TASK_STATUS.WAITING_BINDING,
    user_action_status: 'pending_claim',
    claim_token: claimLinkState.token || task.claim_token || '',
    claim_expires_at: claimLinkState.expiredAt || getTaskClaimExpiresAt(task),
    role_id: '',
    role_name: '',
    role_confirmed_at: null,
    last_error: '已复用原虚拟号刷新绑定链接，请用户使用正确角色重新扫码绑定',
    context_json: JSON.stringify(nextContext),
    updated_at: now,
  })

  await createTaskEvent(
    task.id,
    'kuaishou_cloud_rebind_prepared',
    {
      source,
      attempt,
      mode: 'reuse_existing_vn',
      vnId: oldBinding.vnId,
      vnPhoneMasked: maskPhone(oldBinding.vnPhone),
      bindUrl,
      cloudSourceKey: baseHistoryItem.cloudSourceKey,
      actor,
    },
    now,
  )

  return {
    task: updatedTask,
    claimUrl: claimLinkState.claimUrl,
    token: claimLinkState.token,
    flow: normalizeKuaishouCloudFlow(nextContext.kuaishouCloudFulfillment),
    oldNumberBackStatus: 'skipped' as const,
    mode: 'reuse_existing_vn',
  }
}
