import crypto from 'node:crypto'

import { runtimeConfig } from '../../config/runtime.js'
import {
  countWorkerSmsCodesOnDay,
  createWorkerSmsCode,
  getLatestWorkerSmsCode,
  getWorkerUserByPhone,
} from '../../repositories/worker-platform/index.js'
import type { JsonObject } from '../../types/json.js'
import { createHttpError } from '../../utils/http.js'
import { nowIso } from '../../utils/time.js'
import { getSmsProvider } from '../sms/index.js'

type WorkerSmsPurpose = 'register' | 'password_reset' | 'login'

export async function sendWorkerSmsCode(payload: JsonObject = {}) {
  return sendWorkerSmsCodeForPurpose(payload.phone, 'register')
}

export async function sendWorkerPasswordResetSmsCode(payload: JsonObject = {}) {
  return sendWorkerSmsCodeForPurpose(payload.phone, 'password_reset')
}

export async function sendWorkerLoginSmsCode(payload: JsonObject = {}) {
  return sendWorkerSmsCodeForPurpose(payload.phone, 'login')
}

async function sendWorkerSmsCodeForPurpose(phoneValue: unknown, purpose: WorkerSmsPurpose) {
  const phone = normalizeWorkerPhone(phoneValue)
  const config = runtimeConfig.worker.sms
  const cooldownMs = Math.max(10, Number(config.cooldownSeconds || 60)) * 1000
  const dailyLimit = Math.max(1, Number(config.dailyLimit || 10))
  const ttlSeconds = Math.max(60, Number(config.codeTtlSeconds || 300))
  const worker = await getWorkerUserByPhone(phone)

  if (purpose === 'register' && worker) {
    throw createHttpError('该手机号已注册，请直接登录', {
      statusCode: 409,
      errorCode: 'worker_phone_registered',
    })
  }
  if (purpose !== 'register' && !worker) {
    throw createHttpError('该手机号未注册', {
      statusCode: 404,
      errorCode: 'worker_phone_not_registered',
    })
  }

  const latest = await getLatestWorkerSmsCode(phone, purpose)
  if (
    latest &&
    !latest.consumed_at &&
    new Date(latest.created_at).getTime() > Date.now() - cooldownMs
  ) {
    throw createHttpError('验证码发送过于频繁，请稍后再试', {
      statusCode: 429,
      errorCode: 'worker_sms_cooldown',
    })
  }

  const dayRange = resolveChinaDayRange()
  if ((await countWorkerSmsCodesOnDay(phone, dayRange.start, dayRange.end)) >= dailyLimit) {
    throw createHttpError('今日验证码发送次数已达上限，请明天再试', {
      statusCode: 429,
      errorCode: 'worker_sms_daily_limit',
    })
  }

  const code = generateSmsCode()
  await getSmsProvider().sendCode(phone, code)
  await createWorkerSmsCode({
    phone,
    purpose,
    codeHash: hashSmsCode(code),
    expiresAt: new Date(Date.now() + ttlSeconds * 1000).toISOString(),
    now: nowIso(),
  })
  return {
    sent: true,
    expiresInSeconds: ttlSeconds,
    provider: String(runtimeConfig.worker.sms.provider || 'mock'),
  }
}

export function normalizeWorkerPhone(value: unknown): string {
  const phone = String(value || '').trim()
  if (!/^1[3-9]\d{9}$/.test(phone)) {
    throw createHttpError('手机号格式不正确', {
      statusCode: 400,
      errorCode: 'worker_phone_invalid',
    })
  }
  return phone
}

export function hashSmsCode(code: string): string {
  return crypto
    .createHash('sha256')
    .update(String(code || ''))
    .digest('hex')
}

function generateSmsCode(): string {
  let code = ''
  for (let index = 0; index < 6; index += 1) {
    code += String(crypto.randomInt(10))
  }
  return code
}

function resolveChinaDayRange(): { start: string; end: string } {
  const now = new Date()
  const chinaNow = new Date(now.getTime() + 8 * 60 * 60 * 1000)
  const year = chinaNow.getUTCFullYear()
  const month = chinaNow.getUTCMonth()
  const day = chinaNow.getUTCDate()
  const start = new Date(Date.UTC(year, month, day) - 8 * 60 * 60 * 1000)
  const end = new Date(Date.UTC(year, month, day + 1) - 8 * 60 * 60 * 1000)
  return { start: start.toISOString(), end: end.toISOString() }
}
