import { spawn } from 'node:child_process';
import { chmod, mkdir, readFile, writeFile } from 'node:fs/promises';
import { dirname, join } from 'node:path';

import {
  biomeTemplate,
  eslintTemplate,
  prChecklistTemplate,
  preCommitHookTemplate,
  pyprojectSection,
  rustfmtTemplate,
} from './templates.js';
import type { RailsConfig, ScaffoldResult } from './types.js';

const PYPROJECT_START_MARKER = '# >>> mosaic-quality-rails >>>';
const PYPROJECT_END_MARKER = '# <<< mosaic-quality-rails <<<';

async function ensureDirectory(filePath: string): Promise<void> {
  await mkdir(dirname(filePath), { recursive: true });
}

async function writeRelativeFile(
  projectPath: string,
  relativePath: string,
  contents: string,
  result: ScaffoldResult,
): Promise<void> {
  const absolutePath = join(projectPath, relativePath);
  await ensureDirectory(absolutePath);
  await writeFile(absolutePath, contents, { encoding: 'utf8', mode: 0o644 });
  result.filesWritten.push(relativePath);
}

async function upsertPyproject(
  projectPath: string,
  profile: RailsConfig['profile'],
  result: ScaffoldResult,
): Promise<void> {
  const pyprojectPath = join(projectPath, 'pyproject.toml');
  const nextSection = pyprojectSection(profile);

  let previous = '';
  try {
    previous = await readFile(pyprojectPath, 'utf8');
  } catch {
    previous = '';
  }

  const existingStart = previous.indexOf(PYPROJECT_START_MARKER);
  const existingEnd = previous.indexOf(PYPROJECT_END_MARKER);

  if (existingStart >= 0 && existingEnd > existingStart) {
    const before = previous.slice(0, existingStart).trimEnd();
    const after = previous.slice(existingEnd + PYPROJECT_END_MARKER.length).trimStart();
    const rebuilt = [before, nextSection.trim(), after]
      .filter((segment) => segment.length > 0)
      .join('\n\n');
    await writeRelativeFile(projectPath, 'pyproject.toml', `${rebuilt}\n`, result);
    return;
  }

  const separator = previous.trim().length > 0 ? '\n\n' : '';
  await writeRelativeFile(
    projectPath,
    'pyproject.toml',
    `${previous.trimEnd()}${separator}${nextSection}`,
    result,
  );
}

function runCommand(command: string, args: string[], cwd: string): Promise<void> {
  return new Promise((resolve, reject) => {
    const child = spawn(command, args, {
      cwd,
      stdio: 'ignore',
      env: process.env,
    });

    child.on('error', reject);
    child.on('exit', (code) => {
      if (code === 0) {
        resolve();
        return;
      }
      reject(new Error(`Command failed (${code ?? 'unknown'}): ${command} ${args.join(' ')}`));
    });
  });
}

function buildNodeDevDependencies(config: RailsConfig): string[] {
  const dependencies = new Set<string>();

  if (config.linters.some((linter) => linter.toLowerCase() === 'eslint')) {
    dependencies.add('eslint');
    dependencies.add('@typescript-eslint/parser');
    dependencies.add('@typescript-eslint/eslint-plugin');
  }

  if (config.linters.some((linter) => linter.toLowerCase() === 'biome')) {
    dependencies.add('@biomejs/biome');
  }

  if (config.formatters.some((formatter) => formatter.toLowerCase() === 'prettier')) {
    dependencies.add('prettier');
  }

  if (config.hooks) {
    dependencies.add('husky');
  }

  return [...dependencies];
}

async function installNodeDependencies(config: RailsConfig, result: ScaffoldResult): Promise<void> {
  const dependencies = buildNodeDevDependencies(config);
  if (dependencies.length === 0) {
    return;
  }

  const commandLine = `pnpm add -D ${dependencies.join(' ')}`;

  if (process.env['MOSAIC_QUALITY_RAILS_SKIP_INSTALL'] === '1') {
    result.commandsToRun.push(commandLine);
    return;
  }

  try {
    await runCommand('pnpm', ['add', '-D', ...dependencies], config.projectPath);
  } catch (error) {
    result.warnings.push(
      `Failed to auto-install Node dependencies: ${error instanceof Error ? error.message : String(error)}`,
    );
    result.commandsToRun.push(commandLine);
  }
}

export async function scaffoldQualityRails(config: RailsConfig): Promise<ScaffoldResult> {
  const result: ScaffoldResult = {
    filesWritten: [],
    commandsToRun: [],
    warnings: [],
  };

  const normalizedLinters = new Set(config.linters.map((linter) => linter.toLowerCase()));

  if (config.kind === 'node') {
    if (normalizedLinters.has('eslint')) {
      await writeRelativeFile(
        config.projectPath,
        '.eslintrc',
        eslintTemplate(config.profile),
        result,
      );
    }

    if (normalizedLinters.has('biome')) {
      await writeRelativeFile(
        config.projectPath,
        'biome.json',
        biomeTemplate(config.profile),
        result,
      );
    }

    await installNodeDependencies(config, result);
  }

  if (config.kind === 'python') {
    await upsertPyproject(config.projectPath, config.profile, result);
  }

  if (config.kind === 'rust') {
    await writeRelativeFile(
      config.projectPath,
      'rustfmt.toml',
      rustfmtTemplate(config.profile),
      result,
    );
  }

  if (config.hooks) {
    await writeRelativeFile(
      config.projectPath,
      '.githooks/pre-commit',
      preCommitHookTemplate(config),
      result,
    );
    await chmod(join(config.projectPath, '.githooks/pre-commit'), 0o755);
    result.commandsToRun.push('git config core.hooksPath .githooks');
  }

  await writeRelativeFile(
    config.projectPath,
    'PR-CHECKLIST.md',
    prChecklistTemplate(config.profile),
    result,
  );

  if (config.kind === 'unknown') {
    result.warnings.push(
      'Unable to detect project kind. Generated generic rails only (hooks + PR checklist).',
    );
  }

  return result;
}
