import { tryInjectService, SERVICE_TOKENS, type ServiceToken } from '@/core/di-container'
import { eventBus } from '@/core/event-bus'
import type { Ref } from 'vue'
import { ref } from 'vue'

/**
 * Service metadata for tracking and debugging
 */
export interface ServiceMetadata {
  name: string
  version?: string
  dependencies?: string[]
}

/**
 * Service initialization options
 */
export interface ServiceInitOptions {
  waitForDependencies?: boolean
  maxRetries?: number
  retryDelay?: number
}

/**
 * Base class for all services in the modular architecture
 * Provides standardized dependency injection, initialization, and error handling
 * 
 * @example
 * ```typescript
 * export class MyService extends BaseService {
 *   protected metadata = {
 *     name: 'MyService',
 *     dependencies: ['RelayHub', 'AuthService']
 *   }
 * 
 *   protected async onInitialize(): Promise<void> {
 *     // Service-specific initialization
 *     await this.setupSubscriptions()
 *   }
 * }
 * ```
 */
export abstract class BaseService {
  // Core dependencies with proper typing
  protected relayHub: any = null
  protected authService: any = null
  protected visibilityService: any = null
  protected storageService: any = null
  protected toastService: any = null
  protected invoiceService: any = null
  protected lnbitsAPI: any = null
  
  // Service state
  public readonly isInitialized: Ref<boolean> = ref(false)
  public readonly isInitializing: Ref<boolean> = ref(false)
  public readonly initError: Ref<Error | null> = ref(null)
  
  // Service metadata
  protected abstract readonly metadata: ServiceMetadata
  
  constructor() {
    // Dependencies will be injected after construction
  }
  
  /**
   * Initialize the service with dependency injection and error handling
   */
  public async initialize(options: ServiceInitOptions = {}): Promise<void> {
    const {
      waitForDependencies = true,
      maxRetries = 3,
      retryDelay = 1000
    } = options
    
    if (this.isInitialized.value) {
      console.log(`✅ ${this.metadata.name} already initialized`)
      return
    }
    
    if (this.isInitializing.value) {
      console.log(`⏳ ${this.metadata.name} is already initializing...`)
      return
    }
    
    this.isInitializing.value = true
    this.initError.value = null
    
    try {
      console.log(`🚀 Initializing ${this.metadata.name}...`)
      
      // Inject dependencies
      await this.injectDependencies(waitForDependencies, maxRetries, retryDelay)
      
      // Call service-specific initialization
      await this.onInitialize()
      
      this.isInitialized.value = true
      console.log(`✅ ${this.metadata.name} initialized successfully`)
      
      // Emit initialization event
      eventBus.emit(`service:initialized`, {
        service: this.metadata.name,
        timestamp: Date.now()
      }, this.metadata.name)
      
    } catch (error) {
      const err = error instanceof Error ? error : new Error(String(error))
      this.initError.value = err
      
      console.error(`❌ Failed to initialize ${this.metadata.name}:`, err)
      
      // Emit error event
      eventBus.emit(`service:error`, {
        service: this.metadata.name,
        error: err.message,
        timestamp: Date.now()
      }, this.metadata.name)
      
      throw err
    } finally {
      this.isInitializing.value = false
    }
  }
  
  /**
   * Inject required dependencies with retry logic
   */
  protected async injectDependencies(
    waitForDependencies: boolean,
    maxRetries: number,
    retryDelay: number
  ): Promise<void> {
    let retries = 0
    
    while (retries < maxRetries) {
      try {
        // Try to inject core dependencies
        this.relayHub = tryInjectService(SERVICE_TOKENS.RELAY_HUB)
        this.authService = tryInjectService(SERVICE_TOKENS.AUTH_SERVICE)
        this.visibilityService = tryInjectService(SERVICE_TOKENS.VISIBILITY_SERVICE)
        this.storageService = tryInjectService(SERVICE_TOKENS.STORAGE_SERVICE)
        this.toastService = tryInjectService(SERVICE_TOKENS.TOAST_SERVICE)
        this.invoiceService = tryInjectService(SERVICE_TOKENS.INVOICE_SERVICE)
        this.lnbitsAPI = tryInjectService(SERVICE_TOKENS.LNBITS_API)
        
        // Check if all required dependencies are available
        const missingDeps = this.getMissingDependencies()
        
        if (missingDeps.length === 0) {
          console.log(`✅ All dependencies injected for ${this.metadata.name}`)
          return
        }
        
        if (!waitForDependencies) {
          console.warn(`⚠️ ${this.metadata.name} starting with missing dependencies:`, missingDeps)
          return
        }
        
        if (retries < maxRetries - 1) {
          console.log(`⏳ Waiting for dependencies (${missingDeps.join(', ')})... Retry ${retries + 1}/${maxRetries}`)
          await new Promise(resolve => setTimeout(resolve, retryDelay))
          retries++
        } else {
          throw new Error(`Missing required dependencies: ${missingDeps.join(', ')}`)
        }
        
      } catch (error) {
        if (retries >= maxRetries - 1) {
          throw error
        }
        retries++
      }
    }
  }
  
  /**
   * Get list of missing dependencies
   */
  protected getMissingDependencies(): string[] {
    const missing: string[] = []
    const deps = this.metadata.dependencies || []
    
    if (deps.includes('RelayHub') && !this.relayHub) {
      missing.push('RelayHub')
    }
    if (deps.includes('AuthService') && !this.authService) {
      missing.push('AuthService')
    }
    if (deps.includes('VisibilityService') && !this.visibilityService) {
      missing.push('VisibilityService')
    }
    if (deps.includes('StorageService') && !this.storageService) {
      missing.push('StorageService')
    }
    if (deps.includes('ToastService') && !this.toastService) {
      missing.push('ToastService')
    }
    if (deps.includes('LnbitsAPI') && !this.lnbitsAPI) {
      missing.push('LnbitsAPI')
    }
    
    return missing
  }
  
  /**
   * Wait for specific dependencies to be available
   */
  protected async waitForDependencies(timeout = 10000): Promise<void> {
    const startTime = Date.now()
    
    while (Date.now() - startTime < timeout) {
      const missingDeps = this.getMissingDependencies()
      
      if (missingDeps.length === 0) {
        return
      }
      
      await new Promise(resolve => setTimeout(resolve, 100))
    }
    
    throw new Error(`Timeout waiting for dependencies: ${this.getMissingDependencies().join(', ')}`)
  }
  
  /**
   * Check if user is authenticated (helper method)
   */
  protected requireAuth(): void {
    if (!this.authService?.isAuthenticated?.value) {
      throw new Error('Authentication required')
    }
  }
  
  /**
   * Get current user pubkey (helper method)
   */
  protected getUserPubkey(): string | undefined {
    return this.authService?.user?.value?.pubkey
  }
  
  /**
   * Handle errors consistently across all services
   */
  protected handleError(error: unknown, context: string): Error {
    const err = error instanceof Error ? error : new Error(String(error))
    
    console.error(`[${this.metadata.name}] Error in ${context}:`, err)
    
    // Emit error event for monitoring
    eventBus.emit('service:error', {
      service: this.metadata.name,
      context,
      error: err.message,
      timestamp: Date.now()
    }, this.metadata.name)
    
    return err
  }
  
  /**
   * Log debug information (only in development)
   */
  protected debug(...args: any[]): void {
    if (import.meta.env.DEV) {
      console.log(`[${this.metadata.name}]`, ...args)
    }
  }
  
  /**
   * Abstract method for service-specific initialization
   * Must be implemented by derived classes
   */
  protected abstract onInitialize(): Promise<void>
  
  /**
   * Cleanup method for service disposal
   */
  public async dispose(): Promise<void> {
    try {
      await this.onDispose()
      
      this.isInitialized.value = false
      this.relayHub = null
      this.authService = null
      this.visibilityService = null
      this.storageService = null
      this.toastService = null
      
      console.log(`♻️ ${this.metadata.name} disposed`)
      
    } catch (error) {
      console.error(`Failed to dispose ${this.metadata.name}:`, error)
    }
  }
  
  /**
   * Optional cleanup logic for derived classes
   */
  protected async onDispose(): Promise<void> {
    // Override in derived classes if cleanup is needed
  }
}

/**
 * Type-safe service class with proper dependency typing
 * Use this when you need full type safety for dependencies
 */
export abstract class TypedBaseService<TDeps extends Record<string, any> = {}> extends BaseService {
  protected dependencies!: TDeps
  
  /**
   * Get typed dependency
   */
  protected getDependency<K extends keyof TDeps>(key: K): TDeps[K] {
    return this.dependencies[key]
  }
  
  /**
   * Inject typed dependencies
   */
  protected async injectTypedDependencies(tokens: Record<keyof TDeps, ServiceToken>): Promise<void> {
    this.dependencies = {} as TDeps
    
    for (const [key, token] of Object.entries(tokens)) {
      const service = tryInjectService(token as ServiceToken)
      if (service) {
        (this.dependencies as any)[key] = service
      }
    }
  }
}