import { execFile } from 'node:child_process';
import { promisify } from 'node:util';
import { mkdir, writeFile, realpath } from 'node:fs/promises';
import { existsSync } from 'node:fs';
import { resolve, sep } from 'node:path';
import type { FastifyBaseLogger } from 'fastify';
import type { Config } from '../config.js';
import { createGiteaRepo, GiteaRepoExistsError } from './gitea.js';

const execFileAsync = promisify(execFile);

const GITIGNORE_TEMPLATE = `# OS / editor
.DS_Store
*.swp
*~

# Node
node_modules/
dist/
build/
.env
.env.local

# Python
__pycache__/
*.pyc
.venv/
venv/

# AI agents
.claude/
.opencode/

# Backups
*.bak*
`;

const GIT_USER_NAME = 'indifferentketchup';
const GIT_USER_EMAIL = 'samkintop@gmail.com';

export interface BootstrapResult {
  folder_real_path: string;
  folder_name: string;
  gitea_remote_url: string | null;
  folder_created: boolean;
  git_initialized: boolean;
  first_commit: boolean;
  gitea_remote_created: boolean;
  gitea_pushed: boolean;
  warnings: string[];
}

const SAFE_NAME = /^[a-z0-9][a-z0-9-]{0,63}$/;

export function sanitizeFolderName(raw: string): string {
  return raw
    .toLowerCase()
    .trim()
    .replace(/\s+/g, '-')
    .replace(/[^a-z0-9-]/g, '')
    .replace(/-+/g, '-')
    .replace(/^-+|-+$/g, '')
    .slice(0, 64);
}

export class BootstrapNameError extends Error {}
export class BootstrapCollisionError extends Error {}
export class BootstrapPathError extends Error {}

export async function bootstrapProject(
  config: Config,
  log: FastifyBaseLogger,
  options: {
    name: string;
    commitMessage: string;
    visibility: 'private' | 'public';
    createGiteaRemote: boolean;
  }
): Promise<BootstrapResult> {
  const folder = sanitizeFolderName(options.name);
  if (folder.length === 0 || !SAFE_NAME.test(folder)) {
    throw new BootstrapNameError(`invalid name after sanitization: "${folder}"`);
  }

  // Bootstrap target resolution. Uses BOOTSTRAP_ROOT (writable), not
  // PROJECT_ROOT_WHITELIST (which may be a wider read-only scope for
  // add-existing flow).
  const bootstrapReal = await realpath(config.BOOTSTRAP_ROOT);
  const fullPath = resolve(bootstrapReal, folder);
  if (!fullPath.startsWith(bootstrapReal + sep)) {
    throw new BootstrapPathError('path escapes bootstrap root');
  }
  if (existsSync(fullPath)) {
    throw new BootstrapCollisionError(`path already exists: ${fullPath}`);
  }

  const warnings: string[] = [];
  let folder_created = false;
  let git_initialized = false;
  let first_commit = false;
  let gitea_remote_created = false;
  let gitea_pushed = false;
  let gitea_remote_url: string | null = null;

  await mkdir(fullPath, { recursive: false });
  folder_created = true;
  log.info({ fullPath }, 'project_bootstrap: folder created');

  await writeFile(resolve(fullPath, '.gitignore'), GITIGNORE_TEMPLATE, 'utf8');

  await execFileAsync('git', ['init', '-b', 'main'], { cwd: fullPath });
  git_initialized = true;

  await execFileAsync('git', ['add', '.gitignore'], { cwd: fullPath });
  await execFileAsync(
    'git',
    [
      '-c', `user.name=${GIT_USER_NAME}`,
      '-c', `user.email=${GIT_USER_EMAIL}`,
      'commit',
      '-m', options.commitMessage,
    ],
    { cwd: fullPath }
  );
  first_commit = true;
  log.info({ folder }, 'project_bootstrap: initial commit');

  if (options.createGiteaRemote) {
    if (!config.GITEA_TOKEN) {
      warnings.push('Gitea remote skipped — token not configured');
    } else {
      try {
        const repo = await createGiteaRepo(
          { baseUrl: config.GITEA_BASE_URL, user: config.GITEA_USER, token: config.GITEA_TOKEN },
          folder,
          { private: options.visibility === 'private' }
        );
        gitea_remote_created = true;
        gitea_remote_url = repo.html_url;
        log.info({ folder, html_url: repo.html_url }, 'project_bootstrap: gitea repo created');

        try {
          const sshUrl = repo.ssh_url.replace('git.indifferentketchup.com', '100.114.205.53');
          await execFileAsync('git', ['remote', 'add', 'origin', sshUrl], { cwd: fullPath });
          await execFileAsync('git', ['push', '-u', 'origin', 'main'], { cwd: fullPath });
          gitea_pushed = true;
          log.info({ folder }, 'project_bootstrap: pushed to gitea');
        } catch (pushErr) {
          const msg = pushErr instanceof Error ? pushErr.message : String(pushErr);
          warnings.push(`Push to Gitea failed: ${msg.slice(0, 200)}`);
          log.warn({ err: pushErr, folder }, 'project_bootstrap: push failed');
        }
      } catch (err) {
        if (err instanceof GiteaRepoExistsError) {
          warnings.push('Gitea repo already exists with this name; local repo created without remote');
        } else {
          const msg = err instanceof Error ? err.message : String(err);
          warnings.push(`Gitea remote creation failed: ${msg.slice(0, 200)}`);
        }
        log.warn({ err, folder }, 'project_bootstrap: gitea remote step failed');
      }
    }
  }

  return {
    folder_real_path: fullPath,
    folder_name: folder,
    gitea_remote_url,
    folder_created,
    git_initialized,
    first_commit,
    gitea_remote_created,
    gitea_pushed,
    warnings,
  };
}
