import path from 'path'
import fs from 'fs'
import { logger as rootLogger } from '../config/logger'
import type { PluginInstance, PluginManifest } from './types'

export interface LoadedPlugin {
  instance: PluginInstance
  manifest: PluginManifest
  dir: string
}

const PLUGINS_DIR = process.env.NODE_ENV === 'production'
  ? '/app/plugins'
  : path.resolve(__dirname, '../../../plugins')

/**
 * Ordena plugins por dependências usando topological sort (Kahn's algorithm).
 * Plugins sem dependências vêm primeiro; core plugins antes dos business plugins.
 */
function topoSort(plugins: LoadedPlugin[]): LoadedPlugin[] {
  const byName = new Map(plugins.map((p) => [p.manifest.name, p]))
  const inDegree = new Map<string, number>()
  const dependents = new Map<string, string[]>()

  for (const p of plugins) {
    inDegree.set(p.manifest.name, 0)
    dependents.set(p.manifest.name, [])
  }

  for (const p of plugins) {
    for (const dep of p.manifest.dependencies) {
      if (!byName.has(dep)) {
        rootLogger.warn({ plugin: p.manifest.name, dep }, '[PluginLoader] Dependência não encontrada')
        continue
      }
      dependents.get(dep)!.push(p.manifest.name)
      inDegree.set(p.manifest.name, (inDegree.get(p.manifest.name) ?? 0) + 1)
    }
  }

  const queue = plugins
    .filter((p) => inDegree.get(p.manifest.name) === 0)
    .sort((a, b) => (a.manifest.category === 'core' ? -1 : 1) - (b.manifest.category === 'core' ? -1 : 1))

  const sorted: LoadedPlugin[] = []

  while (queue.length > 0) {
    const current = queue.shift()!
    sorted.push(current)

    for (const depName of dependents.get(current.manifest.name) ?? []) {
      const newDegree = (inDegree.get(depName) ?? 1) - 1
      inDegree.set(depName, newDegree)
      if (newDegree === 0) {
        queue.push(byName.get(depName)!)
      }
    }
  }

  if (sorted.length !== plugins.length) {
    rootLogger.error('[PluginLoader] Dependência circular detectada — carregando na ordem original')
    return plugins
  }

  return sorted
}

/**
 * Escaneia o diretório /plugins, lê cada manifest.json e importa index.ts/js.
 * Retorna plugins ordenados por dependência.
 */
export async function loadPlugins(): Promise<LoadedPlugin[]> {
  if (!fs.existsSync(PLUGINS_DIR)) {
    rootLogger.warn({ dir: PLUGINS_DIR }, '[PluginLoader] Diretório plugins/ não encontrado')
    return []
  }

  const entries = fs.readdirSync(PLUGINS_DIR, { withFileTypes: true })
  const pluginDirs = entries.filter((e) => e.isDirectory()).map((e) => e.name)

  const loaded: LoadedPlugin[] = []

  for (const dirName of pluginDirs) {
    const pluginDir = path.join(PLUGINS_DIR, dirName)
    const manifestPath = path.join(pluginDir, 'manifest.json')
    const indexTs = path.join(pluginDir, 'index.ts')
    const indexJs = path.join(pluginDir, 'index.js')

    if (!fs.existsSync(manifestPath)) {
      rootLogger.warn({ dir: dirName }, '[PluginLoader] manifest.json não encontrado — pulando')
      continue
    }

    // Prefere .js (produção compilada); fallback para .ts (tsx watch em dev)
    const entryPoint = fs.existsSync(indexJs) ? indexJs : fs.existsSync(indexTs) ? indexTs : null
    if (!entryPoint) {
      rootLogger.warn({ dir: dirName }, '[PluginLoader] index.ts/js não encontrado — pulando')
      continue
    }

    try {
      const manifest: PluginManifest = JSON.parse(fs.readFileSync(manifestPath, 'utf-8'))
      const mod = await import(entryPoint)
      const instance: PluginInstance = mod.default ?? mod

      if (typeof instance.activate !== 'function') {
        rootLogger.warn({ dir: dirName }, '[PluginLoader] Plugin sem método activate() — pulando')
        continue
      }

      loaded.push({ instance, manifest, dir: pluginDir })
      rootLogger.info({ name: manifest.name, version: manifest.version }, '[PluginLoader] Plugin carregado')
    } catch (err) {
      rootLogger.error({ err, dir: dirName }, '[PluginLoader] Falha ao carregar plugin')
    }
  }

  return topoSort(loaded)
}
