import { adapters, constants, utils, helpers } from '@cloudbase/utilities'
import { SDKAdapterInterface, CloudbaseAdapter, IRequestConfig } from '@cloudbase/adapter-interface'
import {
  ICloudbaseConfig,
  ICloudbaseUpgradedConfig,
  ICloudbase,
  ICloudbaseExtension,
  KV,
  ICloudbasePlatformInfo,
  EndPointKey,
  ICloudbaseApis,
} from '@cloudbase/types'
import { ICloudbaseAuth } from '@cloudbase/types/auth'
import { registerComponent, registerHook } from './libs/component'
import { getWxDefaultAdapter, Platform } from './libs/adapter'
import { ICloudbaseComponent, ICloudbaseHook } from '@cloudbase/types/component'
import { ICloudbaseCache } from '@cloudbase/types/cache'
import { initCache, getCacheByEnvId, getLocalCache } from './libs/cache'
import { ICloudbaseRequest } from '@cloudbase/types/request'
import { initRequest, getRequestByEnvId } from './libs/request'
import {
  getSdkName,
  setSdkVersion,
  setRegionLevelEndpoint,
  setSdkName,
  setGatewayEndPointWithEnv,
  type ISetEndPointWithKey,
  setEndPointInfo,
  getEndPointInfo,
  getSdkVersion,
  DEFAULT_PROTOCOL,
} from './constants/common'
import { i18nProxy, LANGS } from './libs/lang'
export { getBaseEndPoint } from './constants/common'
export { LANGS } from './libs/lang'
const { useAdapters, useDefaultAdapter } = adapters
const { ERRORS, COMMUNITY_SITE_URL } = constants
const { printWarn } = utils
const { catchErrorsDecorator } = helpers

/**
 * @constant 默认配置
 */
const DEFAULT_INIT_CONFIG: Partial<ICloudbaseConfig> = {
  timeout: 15000,
  persistence: 'local', // 持久化存储类型
}

// timeout上限10分钟
const MAX_TIMEOUT = 1000 * 60 * 10
// timeout下限100ms
const MIN_TIMEOUT = 100

const extensionMap: KV<ICloudbaseExtension> = {}

class Cloudbase implements ICloudbase {
  public authInstance: ICloudbaseAuth
  public oauthInstance: any
  public requestClient: any
  public oauthClient: any
  public version: string
  public auth: ICloudbase['auth'] & ICloudbaseAuth
  public apis: ICloudbaseApis
  private cloudbaseConfig: ICloudbaseConfig

  constructor(config?: ICloudbaseConfig) {
    this.cloudbaseConfig = config ? config : this.cloudbaseConfig
    this.authInstance = null
    this.oauthInstance = null
    this.version = getSdkVersion()
  }

  get config() {
    return this.cloudbaseConfig
  }

  get platform(): ICloudbasePlatformInfo {
    return Platform
  }

  get cache(): ICloudbaseCache {
    return getCacheByEnvId(this.cloudbaseConfig.env)
  }

  get localCache(): ICloudbaseCache {
    return getLocalCache(this.cloudbaseConfig.env)
  }

  get request(): ICloudbaseRequest {
    return getRequestByEnvId(this.cloudbaseConfig.env)
  }

  @catchErrorsDecorator({
    mode: 'sync',
    title: 'Cloudbase 初始化失败',
    messages: [
      '请确认以下各项：',
      '  1 - 调用 cloudbase.init() 的语法或参数是否正确',
      '  2 - 如果是非浏览器环境，是否配置了安全应用来源（https://docs.cloudbase.net/api-reference/webv3/adapter#%E7%AC%AC-2-%E6%AD%A5%E9%85%8D%E7%BD%AE%E5%AE%89%E5%85%A8%E5%BA%94%E7%94%A8%E6%9D%A5%E6%BA%90）',
      `如果问题依然存在，建议到官方问答社区提问或寻找帮助：${COMMUNITY_SITE_URL}`,
    ],
  })
  public init(config: ICloudbaseConfig & { lang?: LANGS } = { env: '' }): Cloudbase {
    config.endPointMode = config.endPointMode || 'GATEWAY'

    // 初始化时若未兼容平台，则使用默认adapter
    if (!Platform.adapter) {
      this.useDefaultAdapter()
    }

    // 在判断config.env之前，先处理node环境的配置
    config = this.dealNodeAdapterConfig(config)

    if (!config.env) {
      throw new Error(JSON.stringify({
        code: ERRORS.INVALID_PARAMS,
        msg: 'env must not be specified',
      }),)
    }

    this.isInitialized(config)

    const reqConfig: IRequestConfig & { auth: ICloudbaseConfig['auth'] } = {
      timeout: config.timeout || 15000,
      timeoutMsg: `[${getSdkName()}][REQUEST TIMEOUT] request had been abort since didn't finished within${
        config.timeout / 1000
      }s`,
      auth: config.auth,
    }

    this.requestClient = new Platform.adapter.reqClass(reqConfig)

    this.cloudbaseConfig = {
      ...DEFAULT_INIT_CONFIG,
      ...config,
      i18n: i18nProxy(Platform, config),
    }

    delete (this.cloudbaseConfig as any).lang
    // 修正timeout取值
    this.cloudbaseConfig.timeout = this.formatTimeout(this.cloudbaseConfig.timeout)
    // 初始化cache和request
    const { env, persistence, debug, timeout, oauthClient, i18n } = this.cloudbaseConfig
    initCache({ env, persistence, debug, platformInfo: this.platform })

    setRegionLevelEndpoint(env, config.region || '')
    setGatewayEndPointWithEnv(env, DEFAULT_PROTOCOL, config.intl)

    const app = new Cloudbase(this.cloudbaseConfig)
    initRequest({
      env,
      region: config.region || '',
      timeout,
      oauthClient,
      _fromApp: app,
      i18n,
      endPointMode: config.endPointMode,
      auth: config.auth,
    })
    app.requestClient = this.requestClient
    ;(this as any)?.fire?.('cloudbase_init', app)

    this.try2InitAuth(app)

    // node环境可以从adapter获取nodeTool的方法
    if (Platform.adapter?.nodeTool) {
      Platform.adapter?.nodeTool(app, this.cloudbaseConfig)
    }

    return app
  }

  public updateConfig(config: ICloudbaseUpgradedConfig) {
    const { persistence, debug } = config
    this.cloudbaseConfig.persistence = persistence
    this.cloudbaseConfig.debug = debug
    // persistence改动影响cache
    initCache({ env: this.cloudbaseConfig.env, persistence, debug, platformInfo: this.platform })
  }

  public updateLang(lang: LANGS) {
    if (!lang || lang === this.cloudbaseConfig.i18n?.lang) return

    this.cloudbaseConfig.i18n.lang = lang
  }

  public registerExtension(ext: ICloudbaseExtension) {
    extensionMap[ext.name] = ext
  }
  @catchErrorsDecorator({
    title: '调用扩展能力失败',
    messages: [
      '请确认以下各项：',
      '  1 - 调用 invokeExtension() 的语法或参数是否正确',
      '  2 - 被调用的扩展能力是否已经安装并通过 registerExtension() 注册',
      `如果问题依然存在，建议到官方问答社区提问或寻找帮助：${COMMUNITY_SITE_URL}`,
    ],
  })
  public async invokeExtension(name: string, opts: any) {
    const ext = extensionMap[name]
    if (!ext) {
      throw new Error(JSON.stringify({
        code: ERRORS.INVALID_PARAMS,
        msg: `extension:${name} must be registered before invoke`,
      }),)
    }

    return await ext.invoke(opts, this)
  }

  public useAdapters(adapters: CloudbaseAdapter | CloudbaseAdapter[], options?: any) {
    const { adapter, runtime } = useAdapters(adapters, options) || {}
    adapter && (Platform.adapter = adapter as SDKAdapterInterface)
    runtime && (Platform.runtime = runtime as string)
  }

  public registerHook(hook: ICloudbaseHook) {
    registerHook(Cloudbase, hook)
  }

  public registerComponent(component: ICloudbaseComponent) {
    registerComponent(Cloudbase, component)
  }

  public registerVersion(version: string) {
    setSdkVersion(version)
    this.version = version
  }

  public registerSdkName(name: string) {
    setSdkName(name)
  }

  /**
   * 检查当前 Cloudbase 实例是否已完成初始化
   *
   * @example
   * ```javascript
   * import cloudbase from '@cloudbase/js-sdk'
   *
   * // 初始化前
   * console.log(cloudbase.isInitialized()) // false
   *
   * const app = cloudbase.init({ env: 'your-env-id' })
   *
   * // 初始化后
   * console.log(app.isInitialized()) // true
   * ```
   *
   * @returns {boolean} 是否已初始化
   */
  public isInitialized(config = this.cloudbaseConfig): boolean {
    // 检测常见占位符 env 值，在开发阶段给出友好提示
    const PLACEHOLDER_PATTERNS = [
      'your-env-id',
      'your-envid',
      'your_env_id',
      'xxx-yyy',
      'xxxx-yyy',
      'envId',
      'env-id',
      'your-environment-id',
      'REPLACE_ME',
    ]
    if (PLACEHOLDER_PATTERNS.some(p => config.env.toLowerCase() === p.toLowerCase())) {
      printWarn(
        ERRORS.INVALID_PARAMS,
        `[CloudBase] 检测到环境 ID "${config.env}" 可能是占位符，请替换为真实的环境 ID。\n`
          + '  获取方式：登录腾讯云开发平台 → 环境管理 → 环境设置 → 环境 ID\n'
          + '  建议通过环境变量配置：process.env.CLOUDBASE_ENV 或 import.meta.env.VITE_CLOUDBASE_ENV',
      )
    }

    return !!config?.env
  }

  /** 设置 tcb api 的 endpoint */
  public registerEndPoint(url: string, protocol?: 'http' | 'https') {
    setEndPointInfo({ baseUrl: url, protocol, env: this.config.env, endPointKey: 'CLOUD_API' })
  }

  /** 设置网关/tcb api的 endPoint，通过 key 指定 */
  public registerEndPointWithKey(props: ISetEndPointWithKey) {
    setEndPointInfo({
      env: this.config.env,
      endPointKey: props.key,
      baseUrl: props.url,
      protocol: props.protocol,
    })
  }

  /** 拿网关/tcb api的 endPoint，通过 key 指定 */
  public getEndPointWithKey(key: EndPointKey) {
    const info = getEndPointInfo(this.config.env, key)
    return {
      BASE_URL: info.baseUrl,
      PROTOCOL: info.protocol,
    }
  }

  // 解析URL参数
  public parseCaptcha(url) {
    return utils.parseCaptcha(url)
  }

  private useDefaultAdapter() {
    const { adapter, runtime } = useDefaultAdapter.bind(this)()
    Platform.adapter = adapter as SDKAdapterInterface
    Platform.runtime = runtime as string
  }

  private formatTimeout(timeout: number) {
    switch (true) {
      case timeout > MAX_TIMEOUT:
        printWarn(ERRORS.INVALID_PARAMS, 'timeout is greater than maximum value[10min]')
        return MAX_TIMEOUT
      case timeout < MIN_TIMEOUT:
        printWarn(ERRORS.INVALID_PARAMS, 'timeout is less than maximum value[100ms]')
        return MIN_TIMEOUT
      default:
        return timeout
    }
  }

  private try2InitAuth(app) {
    try {
      app.auth()
    } catch (error) {
      console.log('try2InitAuth error:', error)
    }
  }

  /**
   * 处理 Node 环境下 adapter 的配置信息
   * 在 Node 环境中，通过 adapter 自动获取密钥、环境ID、accessKey 等配置，
   * 减少用户手动传入配置的负担
   * @param config - 云开发初始化配置对象
   * @returns 补充了 Node adapter 信息后的配置对象
   */
  private dealNodeAdapterConfig(config: ICloudbaseConfig) {
    // node环境可以从adapter获取默认的secretId和secretKey
    if (
      typeof process !== 'undefined'
      && typeof process.env !== 'undefined'
      && !process.env.IS_BROWSER_BUILD
      && Platform.adapter?.getSecretInfo
    ) {
      const secretInfo = Platform.adapter?.getSecretInfo(config)
      // 将 adapter 提供的密钥信息写入 config.auth
      config.auth = {
        ...config.auth,
        secretId: secretInfo.secretId,
        secretKey: secretInfo.secretKey,
        sessionToken: secretInfo.sessionToken,
        secretType: secretInfo.secretType as any,
      }

      // node 环境可能可以从上下文获取env，用户未显式指定时自动填充
      if (!config.env) {
        config.env = secretInfo.env
      }

      // node adapter环境能读取到 process.env.CLOUDBASE_APIKEY 时，在没有传入accessKey时，使用 node adapter 提供的 accessKey（process.env.CLOUDBASE_APIKEY）
      if (!config.accessKey && secretInfo.accessKey) {
        config.accessKey = secretInfo.accessKey
      }
    }

    return config
  }
}

// 类型导出
export type { Cloudbase }

// 值导出
export const cloudbase: ICloudbase = new Cloudbase()

cloudbase.useAdapters(getWxDefaultAdapter())

// 默认导出实例
export default cloudbase
