import * as plugins from './plugins.js';
import * as paths from './paths.js';
import type * as interfaces from './interfaces/index.js';
import { TsViewConfig } from './config/index.js';
import { ViewServer } from './server/index.js';
import { exec } from 'child_process';
import { promisify } from 'util';

const execAsync = promisify(exec);

/**
 * Main TsView class.
 * Provides both CLI and programmatic access to S3 and MongoDB viewing.
 */
export class TsView {
  public config: TsViewConfig;
  public server: ViewServer | null = null;

  private smartbucketInstance: plugins.smartbucket.SmartBucket | null = null;
  private mongoDbConnection: plugins.smartdata.SmartdataDb | null = null;

  constructor() {
    this.config = new TsViewConfig();
  }

  /**
   * Load configuration from .nogit/env.json
   */
  public async loadConfigFromEnv(cwd?: string): Promise<void> {
    await this.config.loadFromEnv(cwd);
  }

  /**
   * Set S3 configuration programmatically
   */
  public setStorageConfig(config: interfaces.IStorageConfig): void {
    this.config.setStorageConfig(config);
  }

  /**
   * Set MongoDB configuration programmatically
   */
  public setMongoConfig(config: interfaces.IMongoConfig): void {
    this.config.setMongoConfig(config);
  }

  /**
   * Get the SmartBucket instance (lazy initialization)
   */
  public async getSmartBucket(): Promise<plugins.smartbucket.SmartBucket | null> {
    if (this.smartbucketInstance) {
      return this.smartbucketInstance;
    }

    const storageConfig = this.config.getStorageConfig();
    if (!storageConfig) {
      return null;
    }

    this.smartbucketInstance = new plugins.smartbucket.SmartBucket({
      endpoint: storageConfig.endpoint,
      port: storageConfig.port,
      accessKey: storageConfig.accessKey,
      accessSecret: storageConfig.accessSecret,
      useSsl: storageConfig.useSsl ?? true,
    });

    return this.smartbucketInstance;
  }

  /**
   * Get the MongoDB connection (lazy initialization)
   */
  public async getMongoDb(): Promise<plugins.smartdata.SmartdataDb | null> {
    if (this.mongoDbConnection) {
      return this.mongoDbConnection;
    }

    const mongoConfig = this.config.getMongoConfig();
    if (!mongoConfig) {
      return null;
    }

    this.mongoDbConnection = new plugins.smartdata.SmartdataDb({
      mongoDbUrl: mongoConfig.mongoDbUrl,
      mongoDbName: mongoConfig.mongoDbName,
    });

    await this.mongoDbConnection.init();
    return this.mongoDbConnection;
  }

  /**
   * Find a free port starting from the given port
   */
  private async findFreePort(startPort: number = 3010): Promise<number> {
    const network = new plugins.smartnetwork.SmartNetwork();
    const freePort = await network.findFreePort(startPort, startPort + 100);
    if (freePort === null) {
      throw new Error(`No free port found between ${startPort} and ${startPort + 100}`);
    }
    return freePort;
  }

  /**
   * Load configuration from smartconfig.json
   */
  private loadSmartconfigConfig(cwd?: string): interfaces.ISmartconfigConfig {
    const smartconfigInstance = new plugins.smartconfig.Smartconfig(cwd || process.cwd());
    const config = smartconfigInstance.dataFor<interfaces.ISmartconfigConfig>('@git.zone/tsview', {});
    return config || {};
  }

  /**
   * Kill process running on the specified port
   */
  private async killProcessOnPort(port: number): Promise<void> {
    try {
      // Get PID using lsof (works on Linux and macOS)
      const { stdout } = await execAsync(`lsof -ti :${port}`);
      const pids = stdout.trim().split('\n').filter(Boolean);
      for (const pid of pids) {
        console.log(`Killing process ${pid} on port ${port}`);
        await execAsync(`kill -9 ${pid}`);
      }
      // Brief wait for port to be released
      await new Promise(resolve => setTimeout(resolve, 500));
    } catch (e) {
      // No process on port or lsof not available, ignore
    }
  }

  /**
   * Start the viewer server
   * @param cliPort - Optional port number from CLI (highest priority)
   */
  public async start(cliPort?: number): Promise<number> {
    const smartconfigConfig = await this.loadSmartconfigConfig();

    let port: number;
    let portWasExplicitlySet = false;

    if (cliPort) {
      // CLI has highest priority
      port = cliPort;
      portWasExplicitlySet = true;
    } else if (smartconfigConfig.port) {
      // Config port specified
      port = smartconfigConfig.port;
      portWasExplicitlySet = true;
    } else {
      // Auto-find free port
      port = await this.findFreePort(3010);
    }

    // Check if port is busy and handle accordingly
    const network = new plugins.smartnetwork.SmartNetwork();
    const isFree = await network.isLocalPortUnused(port);

    if (!isFree) {
      if (smartconfigConfig.killIfBusy) {
        console.log(`Port ${port} is busy. Killing existing process...`);
        await this.killProcessOnPort(port);
      } else if (portWasExplicitlySet) {
        throw new Error(`Port ${port} is busy. Set "killIfBusy": true in smartconfig.json to auto-kill, or use a different port.`);
      } else {
        // Auto port was already free, shouldn't happen, but fallback
        port = await this.findFreePort(port + 1);
      }
    }

    this.server = new ViewServer(this, port);
    await this.server.start();

    console.log(`TsView server started on http://localhost:${port}`);

    // Open browser (default: true, can be disabled via config)
    const shouldOpenBrowser = smartconfigConfig.openBrowser !== false;
    if (shouldOpenBrowser) {
      try {
        await plugins.smartopen.openUrl(`http://localhost:${port}`);
      } catch (err) {
        // Ignore browser open errors
      }
    }

    return port;
  }

  /**
   * Stop the viewer server
   */
  public async stop(): Promise<void> {
    if (this.server) {
      await this.server.stop();
      this.server = null;
    }

    if (this.mongoDbConnection) {
      await this.mongoDbConnection.close();
      this.mongoDbConnection = null;
    }
  }
}
