/**
 * Claude Code Plugin Manager - Conduit-style Plugin System
 *
 * Features:
 * - GitHub-based plugin discovery
 * - Secure plugin installation with validation
 * - Version management and updates
 * - Dependency resolution
 * - Plugin lifecycle management
 */

import fs from 'fs/promises'
import path from 'path'
import { spawn } from 'child_process'
import { createHash } from 'crypto'

// ============================================================================
// TYPES AND INTERFACES
// ============================================================================

export interface PluginMetadata {
  name: string
  version: string
  description: string
  author: string
  license?: string
  repository?: string
  homepage?: string
  keywords?: string[]
  claude: {
    minVersion?: string
    maxVersion?: string
    permissions: string[]
    hooks?: HookDefinition[]
    commands?: CommandDefinition[]
    skills?: SkillDefinition[]
  }
  dependencies?: Record<string, string>
}

export interface HookDefinition {
  event: string
  handler: string
  priority?: number
  condition?: string
}

export interface CommandDefinition {
  name: string
  description: string
  handler: string
  permissions?: string[]
}

export interface SkillDefinition {
  name: string
  description: string
  file: string
}

export interface InstalledPlugin {
  metadata: PluginMetadata
  installPath: string
  version: string
  installedAt: string
  lastUpdated: string
  scope: 'global' | 'project'
  projectPath?: string
  enabled: boolean
  integrity: string
}

export interface MarketplaceSource {
  type: 'github' | 'directory' | 'npm'
  url?: string
  repo?: string
  path?: string
  lastUpdated?: string
}

export interface PluginDiscoveryResult {
  name: string
  description: string
  version: string
  author: string
  source: string
  downloads?: number
  stars?: number
  updated: string
}

// ============================================================================
// PLUGIN MANAGER CLASS
// ============================================================================

export class PluginManager {
  private pluginsDir: string
  private cacheDir: string
  private marketplacesFile: string
  private installedFile: string
  private configDir: string

  constructor(claudeDir: string = path.join(process.env.HOME || '', '.claude')) {
    this.configDir = claudeDir
    this.pluginsDir = path.join(claudeDir, 'plugins')
    this.cacheDir = path.join(this.pluginsDir, 'cache')
    this.marketplacesFile = path.join(this.pluginsDir, 'known_marketplaces.json')
    this.installedFile = path.join(this.pluginsDir, 'installed_plugins.json')
  }

  // ============================================================================
  // INITIALIZATION
  // ============================================================================

  async initialize(): Promise<void> {
    await this.ensureDirectories()
    await this.loadMarketplaces()
    await this.loadInstalledPlugins()
  }

  private async ensureDirectories(): Promise<void> {
    const dirs = [
      this.pluginsDir,
      this.cacheDir,
      path.join(this.pluginsDir, 'marketplaces'),
      path.join(this.pluginsDir, 'tmp'),
    ]

    for (const dir of dirs) {
      try {
        await fs.mkdir(dir, { recursive: true })
      } catch (error) {
        // Directory might already exist
      }
    }
  }

  // ============================================================================
  // MARKETPLACE MANAGEMENT
  // ============================================================================

  async addMarketplace(name: string, source: MarketplaceSource): Promise<void> {
    const marketplaces = await this.loadMarketplaces()

    marketplaces[name] = {
      source,
      installLocation: path.join(this.pluginsDir, 'marketplaces', name),
      lastUpdated: new Date().toISOString(),
    }

    await fs.writeFile(
      this.marketplacesFile,
      JSON.stringify(marketplaces, null, 2)
    )

    // Clone/download marketplace if it's a GitHub repo
    if (source.type === 'github' && source.repo) {
      await this.cloneRepository(
        `https://github.com/${source.repo}.git`,
        path.join(this.pluginsDir, 'marketplaces', name)
      )
    }
  }

  async loadMarketplaces(): Promise<Record<string, any>> {
    try {
      const content = await fs.readFile(this.marketplacesFile, 'utf-8')
      return JSON.parse(content)
    } catch {
      return {}
    }
  }

  // ============================================================================
  // PLUGIN DISCOVERY
  // ============================================================================

  async discoverPlugins(query?: string): Promise<PluginDiscoveryResult[]> {
    const marketplaces = await this.loadMarketplaces()
    const results: PluginDiscoveryResult[] = []

    for (const [name, marketplace]: Object.entries(marketplaces)) {
      const mp = marketplace as any
      const pluginsPath = path.join(mp.installLocation, 'plugins')

      try {
        const pluginDirs = await fs.readdir(pluginsPath)

        for (const pluginDir of pluginDirs) {
          const pluginJsonPath = path.join(
            pluginsPath,
            pluginDir,
            '.claude-plugin',
            'plugin.json'
          )

          try {
            const metadata = JSON.parse(
              await fs.readFile(pluginJsonPath, 'utf-8')
            ) as PluginMetadata

            // Filter by query if provided
            if (
              query &&
              !metadata.name.toLowerCase().includes(query.toLowerCase()) &&
              !metadata.description?.toLowerCase().includes(query.toLowerCase()) &&
              !metadata.keywords?.some((k) =>
                k.toLowerCase().includes(query.toLowerCase())
              )
            ) {
              continue
            }

            results.push({
              name: metadata.name,
              description: metadata.description,
              version: metadata.version,
              author: metadata.author,
              source: name,
              updated: new Date().toISOString(),
            })
          } catch {
            // Skip invalid plugins
          }
        }
      } catch {
        // Marketplace might not have plugins directory
      }
    }

    return results
  }

  // ============================================================================
  // PLUGIN INSTALLATION
  // ============================================================================

  async installPlugin(
    marketplace: string,
    pluginName: string,
    scope: 'global' | 'project' = 'global'
  ): Promise<InstalledPlugin> {
    const marketplaces = await this.loadMarketplaces()
    const mp = marketplaces[marketplace] as any

    if (!mp) {
      throw new Error(`Marketplace "${marketplace}" not found`)
    }

    const sourcePath = path.join(mp.installLocation, 'plugins', pluginName)
    const pluginJsonPath = path.join(sourcePath, '.claude-plugin', 'plugin.json')

    // Read plugin metadata
    const metadata: PluginMetadata = JSON.parse(
      await fs.readFile(pluginJsonPath, 'utf-8')
    )

    // Validate permissions
    await this.validatePermissions(metadata.claude.permissions)

    // Calculate integrity hash
    const integrity = await this.calculateIntegrity(sourcePath)

    // Install to cache
    const versionedPath = path.join(
      this.cacheDir,
      marketplace,
      `${pluginName}-${metadata.version}`
    )
    await fs.mkdir(versionedPath, { recursive: true })

    await this.copyDirectory(sourcePath, versionedPath)

    const installedPlugin: InstalledPlugin = {
      metadata,
      installPath: versionedPath,
      version: metadata.version,
      installedAt: new Date().toISOString(),
      lastUpdated: new Date().toISOString(),
      scope,
      enabled: true,
      integrity,
    }

    // Register plugin
    await this.registerPlugin(installedPlugin)

    // Run install script if present
    const installScript = path.join(sourcePath, 'install.sh')
    try {
      await this.runScript(installScript, versionedPath)
    } catch {
      // No install script or failed
    }

    return installedPlugin
  }

  async installFromGitHub(
    repo: string,
    scope: 'global' | 'project' = 'global'
  ): Promise<InstalledPlugin> {
    const [owner, name] = repo.split('/')
    const tempDir = path.join(this.pluginsDir, 'tmp', `${name}-${Date.now()}`)

    // Clone repository
    await this.cloneRepository(`https://github.com/${repo}.git`, tempDir)

    // Read plugin metadata
    const pluginJsonPath = path.join(tempDir, '.claude-plugin', 'plugin.json')
    const metadata: PluginMetadata = JSON.parse(
      await fs.readFile(pluginJsonPath, 'utf-8')
    )

    // Validate and install
    const integrity = await this.calculateIntegrity(tempDir)
    const versionedPath = path.join(
      this.cacheDir,
      'github',
      `${name}-${metadata.version}`
    )
    await fs.mkdir(versionedPath, { recursive: true })
    await this.copyDirectory(tempDir, versionedPath)

    const installedPlugin: InstalledPlugin = {
      metadata,
      installPath: versionedPath,
      version: metadata.version,
      installedAt: new Date().toISOString(),
      lastUpdated: new Date().toISOString(),
      scope,
      enabled: true,
      integrity,
    }

    await this.registerPlugin(installedPlugin)

    // Cleanup temp dir
    await fs.rm(tempDir, { recursive: true, force: true })

    return installedPlugin
  }

  // ============================================================================
  // PLUGIN MANAGEMENT
  // ============================================================================

  async uninstallPlugin(name: string, marketplace?: string): Promise<void> {
    const installed = await this.loadInstalledPlugins()

    const key = marketplace ? `${name}@${marketplace}` : name

    if (!installed[key]) {
      throw new Error(`Plugin "${name}" is not installed`)
    }

    const plugin = installed[key][0] as InstalledPlugin

    // Run uninstall script if present
    const uninstallScript = path.join(plugin.installPath, 'uninstall.sh')
    try {
      await this.runScript(uninstallScript, plugin.installPath)
    } catch {
      // No uninstall script or failed
    }

    // Remove from cache
    await fs.rm(plugin.installPath, { recursive: true, force: true })

    // Unregister
    delete installed[key]
    await fs.writeFile(
      this.installedFile,
      JSON.stringify({ version: 2, plugins: installed }, null, 2)
    )
  }

  async enablePlugin(name: string, marketplace?: string): Promise<void> {
    const installed = await this.loadInstalledPlugins()
    const key = marketplace ? `${name}@${marketplace}` : name

    if (installed[key]) {
      installed[key][0].enabled = true
      await fs.writeFile(
        this.installedFile,
        JSON.stringify({ version: 2, plugins: installed }, null, 2)
      )
    }
  }

  async disablePlugin(name: string, marketplace?: string): Promise<void> {
    const installed = await this.loadInstalledPlugins()
    const key = marketplace ? `${name}@${marketplace}` : name

    if (installed[key]) {
      installed[key][0].enabled = false
      await fs.writeFile(
        this.installedFile,
        JSON.stringify({ version: 2, plugins: installed }, null, 2)
      )
    }
  }

  async updatePlugin(name: string, marketplace?: string): Promise<void> {
    const installed = await this.loadInstalledPlugins()
    const key = marketplace ? `${name}@${marketplace}` : name

    if (!installed[key]) {
      throw new Error(`Plugin "${name}" is not installed`)
    }

    const plugin = installed[key][0] as InstalledPlugin

    // Reinstall to update
    if (plugin.metadata.repository) {
      await this.installFromGitHub(
        plugin.metadata.repository.replace('https://github.com/', ''),
        plugin.scope
      )
    } else {
      // Reinstall from marketplace
      // Implementation depends on marketplace type
    }
  }

  // ============================================================================
  // PLUGIN LOADING
  // ============================================================================

  async loadInstalledPlugins(): Promise<Record<string, InstalledPlugin[]>> {
    try {
      const content = await fs.readFile(this.installedFile, 'utf-8')
      const data = JSON.parse(content)
      return data.plugins || {}
    } catch {
      return {}
    }
  }

  async getEnabledPlugins(): Promise<InstalledPlugin[]> {
    const installed = await this.loadInstalledPlugins()
    const enabled: InstalledPlugin[] = []

    for (const plugins of Object.values(installed)) {
      for (const plugin of plugins) {
        if (plugin.enabled) {
          enabled.push(plugin as InstalledPlugin)
        }
      }
    }

    return enabled
  }

  // ============================================================================
  // SECURITY
  // ============================================================================

  private async validatePermissions(permissions: string[]): Promise<void> {
    const allowedPermissions = [
      'read:files',
      'write:files',
      'execute:commands',
      'network:request',
      'read:config',
      'write:config',
      'hook:events',
    ]

    for (const perm of permissions) {
      if (!allowedPermissions.includes(perm)) {
        throw new Error(`Unknown permission: ${perm}`)
      }
    }
  }

  private async calculateIntegrity(dirPath: string): Promise<string> {
    const hash = createHash('sha256')
    const files = await this.getAllFiles(dirPath)

    for (const file of files.sort()) {
      const content = await fs.readFile(file)
      hash.update(content)
    }

    return hash.digest('hex')
  }

  private async getAllFiles(dirPath: string): Promise<string[]> {
    const files: string[] = []
    const entries = await fs.readdir(dirPath, { withFileTypes: true })

    for (const entry of entries) {
      const fullPath = path.join(dirPath, entry.name)
      if (entry.isDirectory()) {
        files.push(...(await this.getAllFiles(fullPath)))
      } else {
        files.push(fullPath)
      }
    }

    return files
  }

  // ============================================================================
  // UTILITY FUNCTIONS
  // ============================================================================

  private async registerPlugin(plugin: InstalledPlugin): Promise<void> {
    const installed = await this.loadInstalledPlugins()
    const key = `${plugin.metadata.name}@${plugin.installPath.split('/').slice(-2).join('/')}`

    if (!installed[key]) {
      installed[key] = []
    }

    installed[key].push(plugin)

    await fs.writeFile(
      this.installedFile,
      JSON.stringify({ version: 2, plugins: installed }, null, 2)
    )
  }

  private async cloneRepository(repoUrl: string, targetPath: string): Promise<void> {
    return new Promise((resolve, reject) => {
      const git = spawn('git', ['clone', '--depth', '1', repoUrl, targetPath], {
        stdio: 'inherit',
      })

      git.on('close', (code) => {
        if (code === 0) {
          resolve()
        } else {
          reject(new Error(`Git clone failed with code ${code}`))
        }
      })
    })
  }

  private async copyDirectory(source: string, target: string): Promise<void> {
    await fs.mkdir(target, { recursive: true })
    const entries = await fs.readdir(source, { withFileTypes: true })

    for (const entry of entries) {
      const srcPath = path.join(source, entry.name)
      const destPath = path.join(target, entry.name)

      if (entry.isDirectory()) {
        await this.copyDirectory(srcPath, destPath)
      } else {
        await fs.copyFile(srcPath, destPath)
      }
    }
  }

  private async runScript(scriptPath: string, cwd: string): Promise<void> {
    return new Promise((resolve, reject) => {
      const shell = spawn('bash', [scriptPath], {
        cwd,
        stdio: 'inherit',
      })

      shell.on('close', (code) => {
        if (code === 0) {
          resolve()
        } else {
          reject(new Error(`Script failed with code ${code}`))
        }
      })
    })
  }
}

// ============================================================================
// EXPORTS
// ============================================================================

export default PluginManager
