import http from 'node:http'
import https from 'node:https'

import type { JsonObject } from '../../types/json.js'
import { createHttpError } from '../../utils/http.js'

const DEFAULT_WPUSH_ENDPOINT = 'https://api.wpush.cn/api/v1/send'
const DEFAULT_TIMEOUT_MS = 10000

type NodeHttpResponse = {
  ok: boolean
  status: number
  bodyText: string
}
type WpushSendInput = {
  recipient?: {
    id?: string
    name?: string
    apiKey?: string
  }
  title?: string
  body?: string
  endpoint?: string
}

export async function sendWpushNotification(input: WpushSendInput) {
  const apiKey = String(input.recipient?.apiKey || '').trim()
  const title = String(input.title || '').trim()
  const body = String(input.body || '').trim()
  const endpoint = String(input.endpoint || DEFAULT_WPUSH_ENDPOINT).trim() || DEFAULT_WPUSH_ENDPOINT

  if (!apiKey) {
    throw createHttpError('WPush 接收人缺少 apikey', {
      statusCode: 400,
      errorCode: 'wpush_api_key_required',
    })
  }

  if (!title && !body) {
    throw createHttpError('WPush 通知标题或内容不能为空', {
      statusCode: 400,
      errorCode: 'wpush_message_required',
    })
  }

  const payload = JSON.stringify({
    apikey: apiKey,
    title: title || body,
    content: body || title,
  })
  const controller = new AbortController()
  const timer = setTimeout(() => controller.abort(), DEFAULT_TIMEOUT_MS)

  try {
    const response = await requestViaNodeHttp(new URL(endpoint), {
      method: 'POST',
      headers: {
        'content-type': 'application/json',
        accept: 'application/json, text/plain, */*',
        'content-length': String(Buffer.byteLength(payload)),
      },
      body: payload,
      signal: controller.signal,
    })
    const parsed = parseJsonResponse(response.bodyText)

    if (!response.ok || isWpushFailure(parsed)) {
      throw createHttpError(resolveWpushErrorMessage(parsed, response.bodyText, response.status), {
        statusCode: response.ok ? 502 : response.status,
        errorCode: 'wpush_send_failed',
        context: {
          status: response.status,
          response: parsed || response.bodyText,
        },
      })
    }

    return {
      ok: true,
      status: response.status,
      response: parsed || response.bodyText,
    }
  } catch (error) {
    if (isAbortError(error)) {
      throw createHttpError('WPush 通知发送超时', {
        statusCode: 503,
        errorCode: 'wpush_send_timeout',
        cause: error,
      })
    }

    throw error
  } finally {
    clearTimeout(timer)
  }
}

async function requestViaNodeHttp(
  url: URL,
  {
    method,
    headers,
    body,
    signal,
  }: {
    method: string
    headers: Record<string, string>
    body?: string
    signal?: AbortSignal
  },
): Promise<NodeHttpResponse> {
  const transport = url.protocol === 'https:' ? https : http

  return new Promise((resolve, reject) => {
    const request = transport.request(
      url,
      {
        method,
        headers,
        rejectUnauthorized: false,
      },
      (response) => {
        const chunks: Buffer[] = []

        response.on('data', (chunk) => {
          chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk))
        })

        response.on('end', () => {
          resolve({
            ok: Number(response.statusCode || 0) >= 200 && Number(response.statusCode || 0) < 300,
            status: Number(response.statusCode || 0),
            bodyText: Buffer.concat(chunks).toString('utf8'),
          })
        })
      },
    )

    request.on('error', reject)

    if (signal) {
      if (signal.aborted) {
        const error = new Error('Request aborted')
        error.name = 'AbortError'
        request.destroy(error)
      } else {
        signal.addEventListener(
          'abort',
          () => {
            const error = new Error('Request aborted')
            error.name = 'AbortError'
            request.destroy(error)
          },
          { once: true },
        )
      }
    }

    if (typeof body !== 'undefined') {
      request.write(body)
    }

    request.end()
  })
}

function parseJsonResponse(text: string) {
  if (!text) {
    return null
  }

  try {
    return JSON.parse(text)
  } catch {
    return null
  }
}

function isWpushFailure(parsed: unknown) {
  if (!isPlainObject(parsed)) {
    return false
  }

  const success = parsed.success
  if (typeof success === 'boolean') {
    return success === false
  }

  const code = Number(parsed.code ?? parsed.status ?? 0)
  return Number.isFinite(code) && code !== 0 && code !== 200
}

function resolveWpushErrorMessage(parsed: unknown, responseText: string, status: number) {
  if (isPlainObject(parsed)) {
    return (
      String(parsed.message || parsed.msg || parsed.error || '').trim() ||
      `WPush 通知发送失败，HTTP ${status}`
    )
  }

  return String(responseText || '').trim() || `WPush 通知发送失败，HTTP ${status}`
}

function isAbortError(error: unknown): error is Error {
  return error instanceof Error && error.name === 'AbortError'
}

function isPlainObject(value: unknown): value is JsonObject {
  return Boolean(value) && typeof value === 'object' && !Array.isArray(value)
}
