const path = require('path');
const fs = require('fs');
const { buildTablePlusUrl, getDbDriver, parseLandoDbType } = require('../utils');

function normalizeLandoName(name) {
  return String(name || '').toLowerCase().replace(/[\s-]/g, '');
}

class LandoProvider {
  constructor({ runCommand, runInteractiveCommand }) {
    this.runCommand = runCommand;
    this.runInteractiveCommand = runInteractiveCommand || runCommand;
  }

  async start(project, options = {}) {
    await this.runStartStopCommand('lando', ['start'], project, options);
    return this.status(project);
  }

  async stop(project, options = {}) {
    await this.runStartStopCommand('lando', ['stop', '-y'], project, options);
    return {
      projectId: project.id,
      running: false,
      exposed: false,
      health: 'stopped',
      lastSeenAt: new Date().toISOString()
    };
  }

  async runStartStopCommand(command, args, project, options) {
    const runner = options.interactive ? this.runInteractiveCommand : this.runCommand;
    await runner(command, args, { cwd: project.path });
  }

  async status(project) {
    try {
      const containers = await this.listContainers();
      const projectContainers = this.findProjectContainers(project, containers);
      const running = projectContainers.some(container => container.running);
      const runtime = {
        projectId: project.id,
        running,
        exposed: false,
        health: running ? 'healthy' : 'stopped',
        lastSeenAt: new Date().toISOString()
      };

      if (running) {
        const urls = await this.resolveUrls(project);
        if (urls.length > 0) {
          runtime.localUrl = urls[0];
        }
      }

      return runtime;
    } catch (error) {
      if (error.code === 'ENOENT') {
        throw Object.assign(new Error('Lando is not installed or is not on PATH.'), { code: 'LANDO_NOT_FOUND' });
      }

      return {
        projectId: project.id,
        running: false,
        exposed: false,
        health: 'unknown',
        lastSeenAt: new Date().toISOString()
      };
    }
  }

  async databaseUrl(project) {
    const { stdout } = await this.runCommand('lando', ['info', '--service', 'database', '--format', 'json'], {
      cwd: project.path,
      timeout: 15000,
      maxBuffer: 10 * 1024 * 1024
    });
    const info = JSON.parse(stdout || '[]');

    if (!Array.isArray(info) || info.length === 0) {
      const error = new Error(`No Lando database information is available for ${project.name}.`);
      error.code = 'DATABASE_NOT_AVAILABLE';
      throw error;
    }

    const dbInfo = info[0];
    const dbConfig = {
      host: dbInfo.external_connection?.host || '127.0.0.1',
      port: dbInfo.external_connection?.port || 3306,
      user: dbInfo.creds?.user || 'lando',
      password: dbInfo.creds?.password || 'lando',
      database: dbInfo.creds?.database || 'lando'
    };
    const dbType = this.readDatabaseType(project);
    const connectionName = `${project.name} - Lando`;

    return buildTablePlusUrl(getDbDriver(dbType), dbConfig, connectionName);
  }

  readDatabaseType(project) {
    try {
      const landoYml = fs.readFileSync(path.join(project.path, '.lando.yml'), 'utf8');
      return parseLandoDbType(landoYml)?.type || 'mysql';
    } catch (error) {
      return 'mysql';
    }
  }

  async listContainers() {
    const { stdout } = await this.runCommand('lando', ['list', '--format', 'json'], {
      timeout: 15000,
      maxBuffer: 10 * 1024 * 1024
    });
    return JSON.parse(stdout || '[]');
  }

  findProjectContainers(project, containers) {
    const landoYmlPath = path.join(project.path, '.lando.yml');
    const normalizedName = normalizeLandoName(project.name);

    return containers.filter(container => {
      const sources = Array.isArray(container.src) ? container.src : [];
      const hasSource = sources.some(source => path.resolve(source) === path.resolve(landoYmlPath));
      const hasName = normalizeLandoName(container.app) === normalizedName;
      return container.app !== '_global_' && (hasSource || hasName);
    });
  }

  async resolveUrls(project) {
    try {
      const { stdout } = await this.runCommand('lando', ['info', '--format', 'json'], {
        cwd: project.path,
        timeout: 15000,
        maxBuffer: 10 * 1024 * 1024
      });
      const info = JSON.parse(stdout || '[]');
      const urls = [];

      info.forEach(service => {
        if (Array.isArray(service.urls)) {
          urls.push(...service.urls);
        }
      });

      return [...new Set(urls)];
    } catch (error) {
      return [];
    }
  }
}

module.exports = { LandoProvider, normalizeLandoName };
