import { existsSync } from 'node:fs'
import fsp from 'node:fs/promises'
import path from 'node:path'
import { createFakeProgress, getConfiguration, getLocale, getRootPath, message } from '@vscode-use/utils'
import { ofetch } from 'ofetch'
import { latestVersion } from '@simon_he/latest-version'
import { componentsReducer, propsReducer } from './ui/utils'
import { logger } from './ui-find'

const prefix = '@common-intellisense/'

export const cacheFetch = new Map()
export const localCacheUri = path.resolve(__dirname, 'mapping.json')
let isCommonIntellisenseInProgress = false
let isRemoteUrisInProgress = false
let isLocalUrisInProgress = false
const retry = 1
const timeout = 600000 // 如果 10 分钟拿不到就认为是 proxy 问题
const isZh = getLocale()?.includes('zh')
export const getLocalCache = new Promise((resolve) => {
  if (existsSync(localCacheUri)) {
    fsp.readFile(localCacheUri, 'utf-8').then((res) => {
      logger.info(isZh ? `正在读取 ${localCacheUri} 中的数据` : `Reading data from ${localCacheUri}`)
      try {
        const oldMap = JSON.parse(res) as [string, string][]
        oldMap.forEach(([key, value]) => {
          cacheFetch.set(key, value)
        })
      }
      catch (error) {
        logger.error(String(error))
      }
      resolve('done reading')
      // 列出已有的 key
      const cacheKey = Array.from(cacheFetch.keys()).join(' | ')
      logger.info(isZh ? `缓存读取完毕, 已缓存的 key: ${cacheKey}` : `Cache read complete, cached keys: ${cacheKey}`)
    })
  }
  else {
    resolve('done reading')
  }
})

// todo: add result type replace any
export async function fetchFromCommonIntellisense(tag: string) {
  const name = prefix + tag
  let version = ''

  logger.info(isZh ? `正在查找 ${name} 的最新版本...` : `Looking for the latest version of ${name}...`)
  try {
    version = await latestVersion(name, { cwd: getRootPath(), timeout: 5000, concurrency: 3 })
  }
  catch (error: any) {
    if (error.message.includes('404 Not Found')) {
      // 说明这个版本还未支持, 可以通过 issue 提出
      logger.error(isZh ? `当前版本并未支持` : `The current version is not supported`)
    }
    else {
      logger.error(String(error))
    }
    return
  }

  logger.info(isZh ? `找到 ${name} 的最新版本: ${version}` : `Found the latest version of ${name}: ${version}`)
  const key = `${name}@${version}`
  // 当版本修改是否要删除相同 name 下的其它版本缓存？
  if (isCommonIntellisenseInProgress)
    return

  let resolver: () => void = () => { }
  let rejecter: (msg?: string) => void = () => { }
  isCommonIntellisenseInProgress = true
  if (!cacheFetch.has(key)) {
    createFakeProgress({
      title: isZh ? `正在拉取远程的 ${tag}` : `Pulling remote ${tag}`,
      message: v => isZh ? `已完成 ${v}%` : `Completed ${v}%`,
      callback: (resolve, reject) => {
        resolver = resolve
        rejecter = reject
      },
    })
  }

  try {
    if (cacheFetch.has(key)) {
      logger.info(isZh ? `已缓存的 ${key}` : `cachedKey: ${key}`)
    }
    else {
      logger.info(isZh ? `准备拉取的资源: ${key}` : `ready fetchingKey: ${key}`)
    }

    const scriptContent = cacheFetch.has(key)
      ? cacheFetch.get(key)
      : await Promise.any([
        ofetch(`https://cdn.jsdelivr.net/npm/${key}/dist/index.cjs`, { responseType: 'text', retry, timeout }),
        ofetch(`https://unpkg.com/${key}/dist/index.cjs`, { responseType: 'text', retry, timeout }),
        ofetch(`https://registry.npmmirror.com/${key}/dist/index.cjs`, { responseType: 'text' }),
        ofetch(`https://registry.npmjs.org/${key}/dist/index.cjs`, { responseType: 'text' }),
        ofetch(`https://r.cnpmjs.org/${key}/dist/index.cjs`, { responseType: 'text' }),
        ofetch(`https://cdn.jsdelivr.net/npm/${key}/dist/index.cjs`, { responseType: 'text' }),
      ])
    cacheFetch.set(key, scriptContent)
    const module: any = {}
    const runModule = new Function('module', scriptContent)
    runModule(module)
    const moduleExports = module.exports
    const result: any = {}
    for (const key in moduleExports) {
      const v = moduleExports[key]
      if (key.endsWith('Components')) {
        result[key] = () => componentsReducer(v(isZh))
      }
      else {
        result[key] = () => propsReducer(v())
      }
    }
    resolver()
    isCommonIntellisenseInProgress = false
    return result
  }
  catch (error) {
    rejecter(String(error))
    logger.error(String(error))
    isCommonIntellisenseInProgress = false
    // 尝试从本地获取
    message.error(isZh ? `从远程拉取 UI 包失败 ☹️，请检查代理` : `Failed to pull UI package from remote ☹️, please check the proxy`)
    return fetchFromLocalUris()
    // todo：增加重试机制
  }
}

export async function fetchFromRemoteUrls() {
  // 读区 urls
  const uris = getConfiguration('common-intellisense.remoteUris') as string[]
  if (!uris.length)
    return

  const result: any = {}

  if (isRemoteUrisInProgress)
    return

  let resolver!: () => void
  let rejecter!: (msg?: string) => void
  isRemoteUrisInProgress = true
  createFakeProgress({
    title: isZh ? `正在拉取远程文件` : 'Pulling remote files',
    message: v => isZh ? `已完成 ${v}%` : `Completed ${v}%`,
    callback(resolve, reject) {
      resolver = resolve
      rejecter = reject
    },
  })
  logger.info(isZh ? '从 remoteUris 中拉取数据...' : 'Fetching data from remoteUris...')
  try {
    const scriptContents = await Promise.all(uris.map(async (uri) => {
      logger.info(isZh ? `正在加载 ${uri}` : `Loading ${uri}`)
      return [uri, cacheFetch.has(uri) ? cacheFetch.get(uri) : await ofetch(uri, { responseType: 'text', retry, timeout })]
    }))
    scriptContents.forEach(([uri, scriptContent]) => {
      const module: any = {}
      const runModule = new Function('module', scriptContent)
      cacheFetch.set(uri, scriptContent)
      runModule(module)
      const moduleExports = module.exports
      const temp: any = {}
      const isZh = getLocale()!.includes('zh')
      for (const key in moduleExports) {
        const v = moduleExports[key]
        if (key.endsWith('Components')) {
          temp[key] = () => componentsReducer(v(isZh))
        }
        else {
          temp[key] = () => propsReducer(v())
        }
      }
      Object.assign(result, temp)
    })
    resolver()
  }
  catch (error) {
    rejecter(String(error))
    logger.error(String(error))
  }
  isRemoteUrisInProgress = false

  return result
}

export async function fetchFromRemoteNpmUrls() {
  // 读区 urls
  const uris = getConfiguration('common-intellisense.remoteNpmUris') as string[]
  if (!uris.length)
    return

  const result: any = {}

  if (isRemoteUrisInProgress)
    return

  let resolver!: () => void
  let rejecter!: (msg?: string) => void
  isRemoteUrisInProgress = true
  createFakeProgress({
    title: isZh ? `正在拉取远程 NPM 文件` : 'Pulling remote NPM files',
    message: v => isZh ? `已完成 ${v}%` : `Completed ${v}%`,
    callback(resolve, reject) {
      resolver = resolve
      rejecter = reject
    },
  })
  logger.info(isZh ? '从 remoteNpmUris 中拉取数据...' : 'Fetching data from remoteNpmUris...')

  try {
    const scriptContents = await Promise.all(uris.map(async (key) => {
      logger.info(isZh ? `正在加载 ${key}` : `Loading ${key}`)

      return [key, cacheFetch.has(key)
        ? cacheFetch.get(key)
        : await Promise.any([
          ofetch(`https://cdn.jsdelivr.net/npm/${key}/dist/index.cjs`, { responseType: 'text', retry, timeout }),
          ofetch(`https://unpkg.com/${key}/dist/index.cjs`, { responseType: 'text', retry, timeout }),
          ofetch(`https://registry.npmmirror.com/${key}/dist/index.cjs`, { responseType: 'text' }),
          ofetch(`https://registry.npmjs.org/${key}/dist/index.cjs`, { responseType: 'text' }),
          ofetch(`https://r.cnpmjs.org/${key}/dist/index.cjs`, { responseType: 'text' }),
          ofetch(`https://cdn.jsdelivr.net/npm/${key}/dist/index.cjs`, { responseType: 'text' }),
        ])]
    }))
    scriptContents.forEach(([uri, scriptContent]) => {
      const module: any = {}
      const runModule = new Function('module', scriptContent)
      cacheFetch.set(uri, scriptContent)
      runModule(module)
      const moduleExports = module.exports
      const temp: any = {}
      const isZh = getLocale()!.includes('zh')
      for (const key in moduleExports) {
        const v = moduleExports[key]
        if (key.endsWith('Components')) {
          temp[key] = () => componentsReducer(v(isZh))
        }
        else {
          temp[key] = () => propsReducer(v())
        }
      }
      Object.assign(result, temp)
    })
    resolver()
  }
  catch (error) {
    rejecter(String(error))
    logger.error(String(error))
  }
  isRemoteUrisInProgress = false

  return result
}

export async function fetchFromLocalUris() {
  let uris = getConfiguration('common-intellisense.localUris') as string[]
  if (!uris.length)
    return
  logger.info(`localUris: ${uris}`)
  // 查找本地文件 是否存在
  uris = uris.map((uri) => {
    // 如果是相对路径，转换为绝对路径，否则直接用
    if (uri.startsWith('./'))
      uri = path.resolve(getRootPath()!, uri)

    if (cacheFetch.has(uri) || existsSync(uri)) {
      return uri
    }
    else {
      logger.error(isZh ? `加载本地文件不存在: [${uri}]` : `Local file does not exist: [${uri}]`)
      return false
    }
  }).filter(Boolean) as string[]

  if (!uris.length)
    return

  const result: any = {}
  if (isLocalUrisInProgress)
    return
  let resolver!: () => void
  let rejecter!: (msg?: string) => void
  isLocalUrisInProgress = true
  createFakeProgress({
    title: isZh ? `正在加载本地文件` : 'Loading local files',
    message: v => isZh ? `已完成 ${v}%` : `Completed ${v}%`,
    callback(resolve, reject) {
      resolver = resolve
      rejecter = reject
    },
  })
  try {
    await Promise.all(uris.map(async (uri) => {
      if (cacheFetch.has(uri)) {
        Object.assign(result, cacheFetch.get(uri))
        return
      }
      const module: any = {}
      const scriptContent = await fsp.readFile(uri, 'utf-8')
      cacheFetch.set(uri, scriptContent)
      const runModule = new Function('module', scriptContent)
      runModule(module)
      const moduleExports = module.exports
      const temp: any = {}
      const isZh = getLocale()!.includes('zh')
      for (const key in moduleExports) {
        const v = moduleExports[key]
        if (key.endsWith('Components')) {
          temp[key] = () => componentsReducer(v(isZh))
        }
        else {
          temp[key] = () => propsReducer(v())
        }
      }
      Object.assign(result, temp)
    }))
    resolver()
  }
  catch (error) {
    rejecter(String(error))
    logger.error(String(error))
  }

  isLocalUrisInProgress = false
  return result
}
