import log from 'electron-log';
import { BrowserWindow } from 'electron';
import { execAsync } from '../utils/shell-utils';
import { isNewerVersion } from '../utils/version-utils';

/**
 * Common interface for tool status
 */
export interface BaseToolStatus {
  installed: boolean;
  version: string | null;
  path: string | null;
  updateAvailable: boolean;
  latestVersion: string | null;
}

/**
 * Result of installation/update/reinstall operations
 */
export interface ToolOperationResult {
  success: boolean;
  message: string;
}

/**
 * Configuration for a tool manager
 */
export interface ToolConfig {
  name: string;
  commandName: string; // e.g., 'docker', 'brew', 'frpc'
  versionCommand?: string; // Command to get version, defaults to '<command> --version'
  versionRegex?: RegExp; // Regex to extract version from command output
  possiblePaths?: string[]; // Possible installation paths to check
}

/**
 * Abstract base class for tool managers
 * Provides common functionality for checking status, versions, and updates
 */
export abstract class ToolManagerBase<TStatus extends BaseToolStatus = BaseToolStatus> {
  protected config: ToolConfig;

  constructor(config: ToolConfig) {
    this.config = config;
  }

  /**
   * Check if the tool is installed and get its status
   * Subclasses should override this to provide tool-specific status information
   */
  abstract checkStatus(): Promise<TStatus>;

  /**
   * Check if the tool command is available
   * Returns the path if found, null otherwise
   */
  protected async findToolPath(): Promise<string | null> {
    // First try the command directly
    try {
      const { stdout } = await execAsync(`which ${this.config.commandName}`);
      const path = stdout.trim();
      if (path) {
        log.debug(`Found ${this.config.name} at: ${path}`);
        return path;
      }
    } catch (error) {
      // Command not in PATH, check possible paths
    }

    // Check possible installation paths
    if (this.config.possiblePaths) {
      for (const possiblePath of this.config.possiblePaths) {
        try {
          const { stdout } = await execAsync(`test -f ${possiblePath} && echo "exists"`);
          if (stdout.trim() === 'exists') {
            log.debug(`Found ${this.config.name} at: ${possiblePath}`);
            return possiblePath;
          }
        } catch (error) {
          // Path doesn't exist, continue
        }
      }
    }

    return null;
  }

  /**
   * Get the version of the installed tool
   * Returns null if unable to determine version
   */
  protected async getVersion(): Promise<string | null> {
    try {
      const versionCommand = this.config.versionCommand || `${this.config.commandName} --version`;
      const { stdout } = await execAsync(versionCommand);

      // Try to extract version using regex if provided
      if (this.config.versionRegex) {
        const match = stdout.match(this.config.versionRegex);
        if (match && match[1]) {
          return match[1].trim();
        }
      }

      // Otherwise return the first line of output (common pattern)
      const firstLine = stdout.split('\n')[0].trim();
      return firstLine || null;
    } catch (error: any) {
      log.warn(`Failed to get ${this.config.name} version:`, error.message);
      return null;
    }
  }

  /**
   * Check if an update is available
   * Compares current version with latest version
   */
  protected checkUpdateAvailable(currentVersion: string | null, latestVersion: string | null): boolean {
    if (!currentVersion || !latestVersion) {
      return false;
    }

    try {
      return isNewerVersion(currentVersion, latestVersion);
    } catch (error) {
      log.warn(`Error comparing versions for ${this.config.name}:`, error);
      return false;
    }
  }

  /**
   * Create a basic status object
   * Subclasses can extend this with tool-specific information
   */
  protected createBaseStatus(
    installed: boolean,
    version: string | null = null,
    path: string | null = null,
    latestVersion: string | null = null
  ): BaseToolStatus {
    return {
      installed,
      version,
      path,
      updateAvailable: this.checkUpdateAvailable(version, latestVersion),
      latestVersion
    };
  }

  /**
   * Log tool operation start
   */
  protected logOperationStart(operation: string): void {
    log.info(`Starting ${operation} for ${this.config.name}...`);
  }

  /**
   * Log tool operation success
   */
  protected logOperationSuccess(operation: string): void {
    log.info(`${operation} for ${this.config.name} completed successfully`);
  }

  /**
   * Log tool operation failure
   */
  protected logOperationError(operation: string, error: any): void {
    log.error(`${operation} for ${this.config.name} failed:`, error);
  }

  /**
   * Create a success result
   */
  protected createSuccessResult(message?: string): ToolOperationResult {
    return {
      success: true,
      message: message || `${this.config.name} operation completed successfully`
    };
  }

  /**
   * Create an error result
   */
  protected createErrorResult(error: any): ToolOperationResult {
    const message = error.message || `${this.config.name} operation failed`;
    return {
      success: false,
      message
    };
  }
}

/**
 * Extended base class for tools that can be installed via Homebrew
 */
export abstract class HomebrewToolManager<TStatus extends BaseToolStatus = BaseToolStatus> extends ToolManagerBase<TStatus> {
  protected brewPackageName: string;

  constructor(config: ToolConfig, brewPackageName: string) {
    super(config);
    this.brewPackageName = brewPackageName;
  }

  /**
   * Get latest version available via Homebrew
   */
  protected async getHomebrewLatestVersion(): Promise<string | null> {
    try {
      const { stdout } = await execAsync(`brew info ${this.brewPackageName} --json`);
      const info = JSON.parse(stdout);

      if (Array.isArray(info) && info.length > 0) {
        const latestVersion = info[0].versions?.stable;
        if (latestVersion) {
          log.debug(`Latest ${this.config.name} version from Homebrew: ${latestVersion}`);
          return latestVersion;
        }
      }

      return null;
    } catch (error: any) {
      log.warn(`Failed to get latest ${this.config.name} version from Homebrew:`, error.message);
      return null;
    }
  }

  /**
   * Install tool via Homebrew
   * Subclasses should implement this with tool-specific installation logic
   */
  abstract install(window?: BrowserWindow): Promise<ToolOperationResult>;

  /**
   * Update tool via Homebrew
   * Subclasses should implement this with tool-specific update logic
   */
  abstract update(window?: BrowserWindow): Promise<ToolOperationResult>;

  /**
   * Reinstall tool via Homebrew
   * Subclasses should implement this with tool-specific reinstall logic
   */
  abstract reinstall(window?: BrowserWindow): Promise<ToolOperationResult>;
}
