import fs from 'node:fs';
import path from 'node:path';
import type { AdapterCommand, FrameworkAdapter } from './types.js';

function fileExists(root: string, names: string[]): boolean {
  return names.some((name) => fs.existsSync(path.join(root, name)));
}

function readRequirements(root: string): string {
  for (const name of ['requirements.txt', 'requirements-dev.txt', 'pyproject.toml']) {
    const file = path.join(root, name);
    if (fs.existsSync(file)) return fs.readFileSync(file, 'utf8').toLowerCase();
  }
  return '';
}

/**
 * Django detection stub: install/dev/test via manage.py / common Python tooling.
 * Deeper generators land in a later phase.
 */
export class DjangoAdapter implements FrameworkAdapter {
  readonly kind = 'django' as const;
  readonly label = 'Django';

  constructor(readonly root: string) {}

  installCommand(): AdapterCommand {
    if (fileExists(this.root, ['pyproject.toml']) && fileExists(this.root, ['uv.lock'])) {
      return { command: 'uv', args: ['sync'], cwd: this.root };
    }
    if (fileExists(this.root, ['poetry.lock', 'pyproject.toml'])) {
      return { command: 'poetry', args: ['install'], cwd: this.root };
    }
    if (fileExists(this.root, ['requirements.txt'])) {
      return { command: 'pip', args: ['install', '-r', 'requirements.txt'], cwd: this.root };
    }
    throw new Error(
      `No Django dependency file found at "${this.root}" (expected requirements.txt, poetry.lock, or uv.lock).`,
    );
  }

  devCommand(): AdapterCommand {
    if (!fileExists(this.root, ['manage.py'])) {
      throw new Error(`No manage.py found at "${this.root}".`);
    }
    return { command: 'python', args: ['manage.py', 'runserver', '127.0.0.1:8000'], cwd: this.root };
  }

  buildCommand(): AdapterCommand | null {
    if (!fileExists(this.root, ['manage.py'])) return null;
    return { command: 'python', args: ['manage.py', 'collectstatic', '--noinput'], cwd: this.root };
  }

  testCommand(extraArgs: string[] = []): AdapterCommand | null {
    if (!fileExists(this.root, ['manage.py'])) return null;
    return { command: 'python', args: ['manage.py', 'test', ...extraArgs], cwd: this.root };
  }

  lintCommand(): AdapterCommand | null {
    return null;
  }

  readyPattern(): RegExp {
    return /Starting development server at (https?:\/\/\S+)/i;
  }

  describe(): Record<string, unknown> {
    const req = readRequirements(this.root);
    return {
      kind: this.kind,
      label: this.label,
      root: this.root,
      hasManagePy: fileExists(this.root, ['manage.py']),
      notes: 'install/dev/test + generate_*_resource stubs available.',
      hintsDjango: /django/.test(req),
    };
  }
}

/**
 * Rails detection stub: bundle install + bin/rails server / test.
 */
export class RailsAdapter implements FrameworkAdapter {
  readonly kind = 'rails' as const;
  readonly label = 'Rails';

  constructor(readonly root: string) {}

  installCommand(): AdapterCommand {
    return { command: 'bundle', args: ['install'], cwd: this.root };
  }

  devCommand(): AdapterCommand {
    const binRails = path.join(this.root, 'bin', 'rails');
    if (fs.existsSync(binRails)) {
      return { command: 'ruby', args: [binRails, 'server', '-b', '127.0.0.1', '-p', '3000'], cwd: this.root };
    }
    return { command: 'rails', args: ['server', '-b', '127.0.0.1', '-p', '3000'], cwd: this.root };
  }

  buildCommand(): AdapterCommand | null {
    const binRails = path.join(this.root, 'bin', 'rails');
    if (fs.existsSync(binRails)) {
      return { command: 'ruby', args: [binRails, 'assets:precompile'], cwd: this.root };
    }
    return { command: 'rails', args: ['assets:precompile'], cwd: this.root };
  }

  testCommand(extraArgs: string[] = []): AdapterCommand | null {
    const binRails = path.join(this.root, 'bin', 'rails');
    if (fs.existsSync(binRails)) {
      return { command: 'ruby', args: [binRails, 'test', ...extraArgs], cwd: this.root };
    }
    return { command: 'rails', args: ['test', ...extraArgs], cwd: this.root };
  }

  lintCommand(): AdapterCommand | null {
    return null;
  }

  readyPattern(): RegExp {
    return /Listening on (https?:\/\/\S+)|Puma starting/i;
  }

  describe(): Record<string, unknown> {
    return {
      kind: this.kind,
      label: this.label,
      root: this.root,
      hasBinRails: fs.existsSync(path.join(this.root, 'bin', 'rails')),
      notes: 'install/dev/test + generate_*_resource stubs available.',
    };
  }
}

/**
 * Symfony detection stub: composer + symfony CLI / php -S patterns via composer scripts when present.
 */
export class SymfonyAdapter implements FrameworkAdapter {
  readonly kind = 'symfony' as const;
  readonly label = 'Symfony';

  constructor(readonly root: string) {}

  installCommand(): AdapterCommand {
    return { command: 'composer', args: ['install'], cwd: this.root };
  }

  devCommand(): AdapterCommand {
    // Prefer composer script when present; otherwise PHP built-in server on public/
    const composerPath = path.join(this.root, 'composer.json');
    if (fs.existsSync(composerPath)) {
      try {
        const pkg = JSON.parse(fs.readFileSync(composerPath, 'utf8')) as { scripts?: Record<string, string> };
        if (pkg.scripts?.['serve'] || pkg.scripts?.dev) {
          const script = pkg.scripts.dev ? 'dev' : 'serve';
          return { command: 'composer', args: ['run', script], cwd: this.root };
        }
      } catch {
        // fall through
      }
    }
    return {
      command: 'php',
      args: ['-S', '127.0.0.1:8000', '-t', 'public'],
      cwd: this.root,
    };
  }

  buildCommand(): AdapterCommand | null {
    return { command: 'composer', args: ['install', '--no-dev', '--optimize-autoloader'], cwd: this.root };
  }

  testCommand(extraArgs: string[] = []): AdapterCommand | null {
    const phpunit = path.join(this.root, 'bin', 'phpunit');
    if (fs.existsSync(phpunit) || fs.existsSync(`${phpunit}.bat`)) {
      return { command: 'php', args: ['bin/phpunit', ...extraArgs], cwd: this.root };
    }
    const vendor = path.join(this.root, 'vendor', 'bin', 'phpunit');
    if (fs.existsSync(vendor) || fs.existsSync(`${vendor}.bat`)) {
      return { command: 'php', args: [path.join('vendor', 'bin', 'phpunit'), ...extraArgs], cwd: this.root };
    }
    return null;
  }

  lintCommand(): AdapterCommand | null {
    return null;
  }

  readyPattern(): RegExp {
    return /(?:Development Server|started|listening).*(https?:\/\/\S+)/i;
  }

  describe(): Record<string, unknown> {
    return {
      kind: this.kind,
      label: this.label,
      root: this.root,
      notes: 'install/dev/test + generate_*_resource stubs available.',
    };
  }
}
