import { createTaskEvent } from '../../repositories/task-event-repo.js'
import { updateTask } from '../../repositories/task-repo.js'
import { query as dbQuery } from '../../db/client.js'
import { TASK_STATUS } from '../../domain/task-status.js'
import { logInfo, logWarn } from '../../utils/logger.js'
import { parseJsonObject } from '../../utils/task-json.js'
import { nowIso } from '../../utils/time.js'
import type { TaskRow } from '../../types/repository/rows.js'
import {
  isKuaishouCloudBindingMutationFrozen,
  isKuaishouCloudTask,
  maskPhone,
  normalizeKuaishouCloudFlow,
} from '../fulfillment/kuaishou-cloud/domain.js'
import { refreshKuaishouCloudTaskBindUrl } from '../fulfillment/kuaishou-cloud/prepare-fulfillment.js'
import { returnKuaishouCloudFulfillmentTask } from '../fulfillment/kuaishou-cloud/return-fulfillment.js'

/** bindUrl 过期超过该时长才回收（避免刚过期就触发上游操作） */
const STALE_BIND_URL_GRACE_MS = 15 * 60 * 1000
/** 单轮最多处理的任务数（health 每 2.5 分钟跑一次，防止一次扫太多） */
const RECYCLE_SCAN_LIMIT = 50

type RecycleNumberResult = {
  scanned: number
  skippedFrozen: number
  skippedMissingContext: number
  refreshed: number
  failed: number
  /** 上游判定无权操作（号码已被手动退回/回收/账号换 token 后的孤儿号码），任务绑定已清空 */
  orphanCount: number
  orphanTaskIds: number[]
}

/** 上游"权限不足/没有权限/号码不存在"类错误：号码已不属于当前会话，重试无法恢复，应清空任务绑定 */
function isOrphanNumberError(error: unknown) {
  const message = error instanceof Error ? error.message : String(error || '')
  return (
    message.includes('权限不足') ||
    message.includes('没有权限') ||
    message.includes('不存在') ||
    message.includes('已释放') ||
    message.includes('已回收')
  )
}

/** 清空任务侧已失效的旧绑定（号码已退/已回收），任务下次领取时重新取号 */
async function clearStaleKuaishouCloudBinding(task: TaskRow, errorMessage: string) {
  const now = nowIso()
  const taskContext = parseJsonObject(task.context_json)
  const flow = normalizeKuaishouCloudFlow(taskContext.kuaishouCloudFulfillment)
  const oldVnId = flow.binding.vnId
  const oldVnPhone = flow.binding.vnPhone

  const nextContext = {
    ...taskContext,
    kuaishouCloudFulfillment: {
      ...flow,
      binding: {
        ...flow.binding,
        prepareStatus: 'pending',
        vnId: 0,
        vnPhone: '',
        bindUrl: '',
        bindPreparedAt: null,
        bindExpiresAt: null,
        bindProbeAt: now,
        bindProbeStatus: 'stale_cleared',
        bindProbeMessage: errorMessage,
        roleName: '',
        roleId: '',
      },
      role: {
        status: 'pending',
        name: '',
        rid: '',
        refreshedAt: now,
        errorMessage: '旧绑定号码已失效，已清空绑定，将重新取号',
        rawInfo: null,
      },
    },
  }

  await updateTask(task.id, {
    task_status: TASK_STATUS.PENDING_BINDING_PREPARE,
    role_id: '',
    role_name: '',
    last_error: `旧绑定号码已失效（${errorMessage}），已清空绑定`,
    context_json: JSON.stringify(nextContext),
    updated_at: now,
  })

  await createTaskEvent(
    task.id,
    'kuaishou_cloud_stale_binding_cleared',
    {
      source: 'scheduler_stale_number_recycle',
      oldVnId,
      oldVnPhoneMasked: maskPhone(oldVnPhone),
      errorMessage,
      actor: { source: 'system' },
    },
    now,
  )
}

export async function recycleCloudtentaclesStaleNumbers(): Promise<RecycleNumberResult> {
  const staleBeforeIso = new Date(Date.now() - STALE_BIND_URL_GRACE_MS).toISOString()
  const result: RecycleNumberResult = {
    scanned: 0,
    skippedFrozen: 0,
    skippedMissingContext: 0,
    refreshed: 0,
    failed: 0,
    orphanCount: 0,
    orphanTaskIds: [],
  }

  const rows = await dbQuery<TaskRow>(
    `
      SELECT *
      FROM fulfillment_tasks ft
      WHERE ft.executor_key = 'kuaishou_ct_assisted'
        AND ft.task_status NOT IN ('redeeming', 'dispatched_pending_return', 'completed', 'redeemed', 'expired', 'closed')
        AND COALESCE(ft.context_json #>> '{kuaishouCloudFulfillment,dispatch,status}', '') <> 'success'
        AND COALESCE(ft.context_json #>> '{kuaishouCloudFulfillment,binding,prepareStatus}', '') = 'ready'
        AND COALESCE(ft.context_json #>> '{kuaishouCloudFulfillment,binding,bindExpiresAt}', '') <> ''
        AND (ft.context_json #>> '{kuaishouCloudFulfillment,binding,bindExpiresAt}')::timestamptz < $1
      ORDER BY ft.id DESC
      LIMIT $2
    `,
    [staleBeforeIso, RECYCLE_SCAN_LIMIT],
  )

  for (const task of rows.rows) {
    result.scanned += 1
    const taskId = Number(task.id || 0)

    try {
      if (!isKuaishouCloudTask(task)) {
        result.skippedMissingContext += 1
        continue
      }

      const flow = normalizeKuaishouCloudFlow(
        parseJsonObject(task.context_json).kuaishouCloudFulfillment,
      )

      // 已发货/兑换中：禁止换号覆盖，跳过
      if (isKuaishouCloudBindingMutationFrozen(task, flow)) {
        result.skippedFrozen += 1
        continue
      }

      if (!flow.binding.vnId || !flow.binding.vnKey || !flow.binding.bindUrl) {
        result.skippedMissingContext += 1
        continue
      }

      await refreshKuaishouCloudTaskBindUrl(task, {
        source: 'scheduler_stale_number_recycle',
        actor: { source: 'system' },
      })
      result.refreshed += 1
    } catch (error) {
      const message = error instanceof Error ? error.message : String(error || '')
      if (isOrphanNumberError(error)) {
        result.orphanCount += 1
        result.orphanTaskIds.push(taskId)
        try {
          await clearStaleKuaishouCloudBinding(task, message)
          logWarn(
            '[scheduler/cloudtentacles-number-recycle]',
            '旧绑定号码无操作权限（号码已退/已回收），已清空任务绑定，等待重新取号',
            { taskId, error: message },
          )
        } catch (clearError) {
          result.failed += 1
          logWarn('[scheduler/cloudtentacles-number-recycle]', '清空旧绑定失败', {
            taskId,
            error: clearError instanceof Error ? clearError.message : String(clearError || ''),
          })
        }
        continue
      }
      result.failed += 1
      logWarn('[scheduler/cloudtentacles-number-recycle]', '过期绑定资源回收失败', {
        taskId,
        error: message,
      })
    }
  }

  if (result.scanned > 0) {
    logInfo('[scheduler/cloudtentacles-number-recycle]', '过期绑定资源回收完成', {
      ...result,
      taskIds: rows.rows.map((row) => Number(row.id || 0)),
    })
  }

  return result
}

/** 重试发货后未完成的退号（DISPATCHED_PENDING_RETURN 且未退号成功） */
export async function retryCloudtentaclesPendingReturns(): Promise<{
  scanned: number
  returned: number
  failed: number
  orphanCount: number
  orphanTaskIds: number[]
}> {
  const result = {
    scanned: 0,
    returned: 0,
    failed: 0,
    orphanCount: 0,
    orphanTaskIds: [] as number[],
  }
  const rows = await dbQuery<TaskRow>(
    `
      SELECT *
      FROM fulfillment_tasks ft
      WHERE ft.executor_key = 'kuaishou_ct_assisted'
        AND ft.task_status = $1
        AND COALESCE(ft.context_json #>> '{kuaishouCloudFulfillment,returnNumber,status}', '') <> 'success'
      ORDER BY ft.id DESC
      LIMIT $2
    `,
    [TASK_STATUS.DISPATCHED_PENDING_RETURN, RECYCLE_SCAN_LIMIT],
  )

  for (const task of rows.rows) {
    result.scanned += 1
    const taskId = Number(task.id || 0)

    try {
      if (!isKuaishouCloudTask(task)) {
        result.failed += 1
        continue
      }
      const flow = normalizeKuaishouCloudFlow(
        parseJsonObject(task.context_json).kuaishouCloudFulfillment,
      )
      if (!flow.binding.vnId || !flow.binding.vnKey) {
        result.failed += 1
        continue
      }

      await returnKuaishouCloudFulfillmentTask(task, {
        source: 'scheduler_retry_return_number',
        actor: { source: 'system' },
      })
      result.returned += 1
    } catch (error) {
      const message = error instanceof Error ? error.message : String(error || '')
      if (isOrphanNumberError(error)) {
        result.orphanCount += 1
        result.orphanTaskIds.push(taskId)
        logWarn(
          '[scheduler/cloudtentacles-number-recycle]',
          '发货收尾退号无操作权限（号码已退/已回收），请人工确认任务收尾',
          { taskId, error: message },
        )
        continue
      }
      result.failed += 1
      logWarn('[scheduler/cloudtentacles-number-recycle]', '发货后退号重试失败', {
        taskId,
        error: message,
      })
    }
  }

  if (result.scanned > 0) {
    logInfo('[scheduler/cloudtentacles-number-recycle]', '发货后退号重试完成', {
      ...result,
      taskIds: rows.rows.map((row) => Number(row.id || 0)),
    })
  }

  return result
}
