/**
 * Augmentation Discovery API
 * 
 * Provides discovery and configuration capabilities for augmentations
 * Enables tools like brain-cloud to dynamically discover, configure, and manage augmentations
 */

import { AugmentationRegistry } from './brainyAugmentation.js'
import { AugmentationManifest, JSONSchema } from './manifest.js'
import { AugmentationConfigResolver } from './configResolver.js'

/**
 * Augmentation listing with manifest and status
 */
export interface AugmentationListing {
  id: string
  name: string
  manifest: AugmentationManifest
  status: {
    enabled: boolean
    initialized: boolean
    category: string
    priority: number
  }
  config?: {
    current: any
    schema?: JSONSchema
    sources?: any[]
  }
}

/**
 * Configuration validation result
 */
export interface ConfigValidationResult {
  valid: boolean
  errors?: string[]
  warnings?: string[]
  suggestions?: string[]
}

/**
 * Discovery API options
 */
export interface DiscoveryOptions {
  includeConfig?: boolean
  includeSchema?: boolean
  includeSources?: boolean
  category?: string
  enabled?: boolean
}

/**
 * Augmentation Discovery API
 * 
 * Provides a unified interface for discovering and managing augmentations
 */
export class AugmentationDiscovery {
  constructor(private registry: AugmentationRegistry) {}
  
  /**
   * Discover all registered augmentations with manifests
   * @param options Discovery options
   * @returns List of augmentation listings
   */
  async discover(options: DiscoveryOptions = {}): Promise<AugmentationListing[]> {
    const augmentations = this.registry.getAll()
    const listings: AugmentationListing[] = []
    
    for (const aug of augmentations) {
      // Check if augmentation has manifest support
      const hasManifest = 'getManifest' in aug && typeof aug.getManifest === 'function'
      
      if (!hasManifest) {
        // Skip augmentations without manifest support (legacy)
        continue
      }
      
      try {
        // Check if augmentation has manifest method
        if (!('getManifest' in aug) || typeof aug.getManifest !== 'function') {
          continue
        }
        const getManifestFn = aug.getManifest as Function
        const manifest = getManifestFn()
        
        // Apply filters
        if (options.category && manifest.category !== options.category) {
          continue
        }
        
        if (options.enabled !== undefined) {
          const isEnabled = (aug as any).enabled !== false
          if (isEnabled !== options.enabled) {
            continue
          }
        }
        
        // Build listing
        const listing: AugmentationListing = {
          id: manifest.id,
          name: manifest.name,
          manifest,
          status: {
            enabled: (aug as any).enabled !== false,
            initialized: (aug as any).isInitialized || false,
            category: (aug as any).category || manifest.category,
            priority: aug.priority
          }
        }
        
        // Include configuration if requested
        if (options.includeConfig && 'getConfig' in aug) {
          const getConfigFn = aug.getConfig as Function
          listing.config = {
            current: getConfigFn()
          }
          
          if (options.includeSchema) {
            listing.config.schema = manifest.configSchema
          }
        }
        
        listings.push(listing)
      } catch (error) {
        console.warn(`Failed to get manifest for augmentation ${aug.name}:`, error)
      }
    }
    
    // Sort by priority (highest first) then by name
    listings.sort((a, b) => {
      const priorityDiff = b.status.priority - a.status.priority
      if (priorityDiff !== 0) return priorityDiff
      return a.name.localeCompare(b.name)
    })
    
    return listings
  }
  
  /**
   * Get a specific augmentation's manifest
   * @param augId Augmentation ID
   * @returns Augmentation manifest or null if not found
   */
  async getManifest(augId: string): Promise<AugmentationManifest | null> {
    const aug = this.registry.get(augId)
    
    if (!aug || !('getManifest' in aug)) {
      return null
    }
    
    try {
      const getManifestFn = aug.getManifest as Function
      return getManifestFn()
    } catch (error) {
      console.error(`Failed to get manifest for ${augId}:`, error)
      return null
    }
  }
  
  /**
   * Get configuration schema for an augmentation
   * @param augId Augmentation ID
   * @returns Configuration schema or null
   */
  async getConfigSchema(augId: string): Promise<JSONSchema | null> {
    const manifest = await this.getManifest(augId)
    return manifest?.configSchema || null
  }
  
  /**
   * Get current configuration for an augmentation
   * @param augId Augmentation ID
   * @returns Current configuration or null
   */
  async getConfig(augId: string): Promise<any | null> {
    const aug = this.registry.get(augId)
    
    if (!aug || !('getConfig' in aug)) {
      return null
    }
    
    try {
      const getConfigFn = aug.getConfig as Function
      return getConfigFn()
    } catch (error) {
      console.error(`Failed to get config for ${augId}:`, error)
      return null
    }
  }
  
  /**
   * Update configuration for an augmentation
   * @param augId Augmentation ID
   * @param config New configuration
   * @returns Updated configuration or null on failure
   */
  async updateConfig(augId: string, config: any): Promise<any | null> {
    const aug = this.registry.get(augId)
    
    if (!aug || !('updateConfig' in aug) || !('getConfig' in aug)) {
      throw new Error(`Augmentation ${augId} does not support configuration updates`)
    }
    
    try {
      const updateConfigFn = aug.updateConfig as Function
      await updateConfigFn(config)
      const getConfigFn = aug.getConfig as Function
      return getConfigFn()
    } catch (error) {
      throw new Error(`Failed to update config for ${augId}: ${error}`)
    }
  }
  
  /**
   * Validate configuration against schema
   * @param augId Augmentation ID
   * @param config Configuration to validate
   * @returns Validation result
   */
  async validateConfig(augId: string, config: any): Promise<ConfigValidationResult> {
    const schema = await this.getConfigSchema(augId)
    
    if (!schema) {
      return {
        valid: true,
        warnings: ['No schema available for validation']
      }
    }
    
    const errors: string[] = []
    const warnings: string[] = []
    const suggestions: string[] = []
    
    // Check required fields
    if (schema.required) {
      for (const field of schema.required) {
        if (config[field] === undefined) {
          errors.push(`Missing required field: ${field}`)
        }
      }
    }
    
    // Validate properties
    if (schema.properties) {
      for (const [key, propSchema] of Object.entries(schema.properties)) {
        const value = config[key]
        
        if (value === undefined) {
          // Check if there's a default
          if (propSchema.default !== undefined) {
            suggestions.push(`Field '${key}' not provided, will use default: ${JSON.stringify(propSchema.default)}`)
          }
          continue
        }
        
        // Type validation
        if (propSchema.type) {
          const actualType = Array.isArray(value) ? 'array' : typeof value
          if (actualType !== propSchema.type) {
            errors.push(`${key}: expected ${propSchema.type}, got ${actualType}`)
          }
        }
        
        // Additional validations for specific types
        this.validatePropertyValue(key, value, propSchema, errors, warnings)
      }
    }
    
    // Check for unknown properties
    if (schema.additionalProperties === false && schema.properties) {
      const allowedKeys = Object.keys(schema.properties)
      for (const key of Object.keys(config)) {
        if (!allowedKeys.includes(key)) {
          warnings.push(`Unknown property: ${key}`)
        }
      }
    }
    
    return {
      valid: errors.length === 0,
      errors: errors.length > 0 ? errors : undefined,
      warnings: warnings.length > 0 ? warnings : undefined,
      suggestions: suggestions.length > 0 ? suggestions : undefined
    }
  }
  
  /**
   * Validate a property value against its schema
   */
  private validatePropertyValue(
    key: string,
    value: any,
    schema: JSONSchema,
    errors: string[],
    warnings: string[]
  ): void {
    // Number validations
    if (schema.type === 'number') {
      if (schema.minimum !== undefined && value < schema.minimum) {
        errors.push(`${key}: value ${value} is less than minimum ${schema.minimum}`)
      }
      if (schema.maximum !== undefined && value > schema.maximum) {
        errors.push(`${key}: value ${value} is greater than maximum ${schema.maximum}`)
      }
    }
    
    // String validations
    if (schema.type === 'string') {
      if (schema.minLength !== undefined && value.length < schema.minLength) {
        errors.push(`${key}: length ${value.length} is less than minimum ${schema.minLength}`)
      }
      if (schema.maxLength !== undefined && value.length > schema.maxLength) {
        errors.push(`${key}: length ${value.length} is greater than maximum ${schema.maxLength}`)
      }
      if (schema.pattern) {
        const regex = new RegExp(schema.pattern)
        if (!regex.test(value)) {
          errors.push(`${key}: value does not match pattern ${schema.pattern}`)
        }
      }
    }
    
    // Enum validation
    if (schema.enum && !schema.enum.includes(value)) {
      errors.push(`${key}: value '${value}' is not one of allowed values: ${schema.enum.join(', ')}`)
    }
  }
  
  /**
   * Get environment variables for an augmentation
   * @param augId Augmentation ID
   * @returns Map of environment variable names to descriptions
   */
  async getEnvironmentVariables(augId: string): Promise<Record<string, any> | null> {
    const manifest = await this.getManifest(augId)
    
    if (!manifest?.configSchema?.properties) {
      return null
    }
    
    const prefix = `BRAINY_AUG_${augId.toUpperCase()}_`
    const vars: Record<string, any> = {}
    
    for (const [key, prop] of Object.entries(manifest.configSchema.properties)) {
      const envKey = prefix + key.replace(/([A-Z])/g, '_$1').toUpperCase()
      vars[envKey] = {
        configKey: key,
        description: prop.description,
        type: prop.type,
        default: prop.default,
        required: manifest.configSchema.required?.includes(key),
        currentValue: typeof process !== 'undefined' ? process.env?.[envKey] : undefined
      }
    }
    
    return vars
  }
  
  /**
   * Get configuration examples for an augmentation
   * @param augId Augmentation ID
   * @returns Configuration examples or empty array
   */
  async getConfigExamples(augId: string): Promise<any[]> {
    const manifest = await this.getManifest(augId)
    return manifest?.configExamples || []
  }
  
  /**
   * Check if an augmentation supports configuration
   * @param augId Augmentation ID
   * @returns True if augmentation supports configuration
   */
  async supportsConfiguration(augId: string): Promise<boolean> {
    const aug = this.registry.get(augId)
    return !!(aug && 'getConfig' in aug && 'updateConfig' in aug)
  }
  
  /**
   * Get augmentations by category
   * @param category Category to filter by
   * @returns List of augmentations in the category
   */
  async getByCategory(category: string): Promise<AugmentationListing[]> {
    return this.discover({ category })
  }
  
  /**
   * Get enabled augmentations
   * @returns List of enabled augmentations
   */
  async getEnabled(): Promise<AugmentationListing[]> {
    return this.discover({ enabled: true })
  }
  
  /**
   * Search augmentations by keyword
   * @param query Search query
   * @returns Matching augmentations
   */
  async search(query: string): Promise<AugmentationListing[]> {
    const all = await this.discover()
    const queryLower = query.toLowerCase()
    
    return all.filter(listing => {
      const manifest = listing.manifest
      
      // Search in various fields
      const searchFields = [
        manifest.name,
        manifest.description,
        manifest.longDescription,
        ...(manifest.keywords || []),
        manifest.category
      ].filter(Boolean).map(s => s!.toLowerCase())
      
      return searchFields.some(field => field.includes(queryLower))
    })
  }
  
  /**
   * Export configuration for all augmentations
   * @returns Map of augmentation IDs to configurations
   */
  async exportConfigurations(): Promise<Record<string, any>> {
    const configs: Record<string, any> = {}
    const listings = await this.discover({ includeConfig: true })
    
    for (const listing of listings) {
      if (listing.config?.current) {
        configs[listing.id] = listing.config.current
      }
    }
    
    return configs
  }
  
  /**
   * Import configurations for multiple augmentations
   * @param configs Map of augmentation IDs to configurations
   * @returns Results of import operation
   */
  async importConfigurations(configs: Record<string, any>): Promise<Record<string, { success: boolean; error?: string }>> {
    const results: Record<string, { success: boolean; error?: string }> = {}
    
    for (const [augId, config] of Object.entries(configs)) {
      try {
        // Validate before applying
        const validation = await this.validateConfig(augId, config)
        
        if (!validation.valid) {
          results[augId] = {
            success: false,
            error: `Validation failed: ${validation.errors?.join(', ')}`
          }
          continue
        }
        
        // Apply configuration
        await this.updateConfig(augId, config)
        results[augId] = { success: true }
      } catch (error) {
        results[augId] = {
          success: false,
          error: error instanceof Error ? error.message : String(error)
        }
      }
    }
    
    return results
  }
  
  /**
   * Generate configuration documentation
   * @param augId Augmentation ID
   * @returns Markdown documentation
   */
  async generateConfigDocs(augId: string): Promise<string | null> {
    const manifest = await this.getManifest(augId)
    if (!manifest) return null
    
    const schema = manifest.configSchema
    const examples = manifest.configExamples || []
    const envVars = await this.getEnvironmentVariables(augId)
    
    let docs = `# ${manifest.name} Configuration\n\n`
    docs += `${manifest.description}\n\n`
    
    if (manifest.longDescription) {
      docs += `## Overview\n\n${manifest.longDescription}\n\n`
    }
    
    // Configuration options
    if (schema?.properties) {
      docs += `## Configuration Options\n\n`
      
      for (const [key, prop] of Object.entries(schema.properties)) {
        const required = schema.required?.includes(key) ? ' *(required)*' : ''
        docs += `### \`${key}\`${required}\n\n`
        
        if (prop.description) {
          docs += `${prop.description}\n\n`
        }
        
        docs += `- **Type**: ${prop.type}\n`
        
        if (prop.default !== undefined) {
          docs += `- **Default**: \`${JSON.stringify(prop.default)}\`\n`
        }
        
        if (prop.minimum !== undefined) {
          docs += `- **Minimum**: ${prop.minimum}\n`
        }
        
        if (prop.maximum !== undefined) {
          docs += `- **Maximum**: ${prop.maximum}\n`
        }
        
        if (prop.enum) {
          docs += `- **Allowed values**: ${prop.enum.map(v => `\`${v}\``).join(', ')}\n`
        }
        
        docs += '\n'
      }
    }
    
    // Environment variables
    if (envVars && Object.keys(envVars).length > 0) {
      docs += `## Environment Variables\n\n`
      docs += `| Variable | Config Key | Type | Required | Default |\n`
      docs += `|----------|------------|------|----------|----------|\n`
      
      for (const [envKey, info] of Object.entries(envVars)) {
        docs += `| \`${envKey}\` | ${info.configKey} | ${info.type} | ${info.required ? 'Yes' : 'No'} | ${info.default !== undefined ? `\`${info.default}\`` : '-'} |\n`
      }
      
      docs += '\n'
    }
    
    // Examples
    if (examples.length > 0) {
      docs += `## Examples\n\n`
      
      for (const example of examples) {
        docs += `### ${example.name}\n\n`
        
        if (example.description) {
          docs += `${example.description}\n\n`
        }
        
        docs += '```json\n'
        docs += JSON.stringify(example.config, null, 2)
        docs += '\n```\n\n'
      }
    }
    
    return docs
  }
}