import { mkdtempSync, existsSync, rmSync } from 'node:fs';
import { tmpdir } from 'node:os';
import { join } from 'node:path';
import { afterAll, beforeAll, describe, expect, it, vi } from 'vitest';
import { buildApp, type AppBundle } from '../src/app.js';
import { openDb, type Db } from '../src/db/index.js';
import type { Config } from '../src/config.js';
import type { CreateProjectResponse } from '@arboretum/shared';

// On NE mocke PAS node:child_process : on veut un vrai `git init` (testé via command:'bash', qui
// n'a pas besoin de résoudre le binaire claude). Seul le PTY est simulé (pas de vrai process en CI).
vi.mock('@homebridge/node-pty-prebuilt-multiarch', () => {
  class FakePty {
    pid = 424242;
    write = vi.fn();
    resize = vi.fn();
    pause = vi.fn();
    resume = vi.fn();
    kill = vi.fn();
    onData(): { dispose: () => void } {
      return { dispose: () => {} };
    }
    onExit(): { dispose: () => void } {
      return { dispose: () => {} };
    }
  }
  return { default: { spawn: (): FakePty => new FakePty() } };
});

process.env.ARBORETUM_LOG = 'silent';

let dir: string;
let root: string;
let bundle: AppBundle;
let db: Db;
let token: string;

const auth = (): { authorization: string } => ({ authorization: `Bearer ${token}` });

beforeAll(() => {
  dir = mkdtempSync(join(tmpdir(), 'arboretum-projects-'));
  root = dir; // la racine existe déjà

  const dbPath = join(dir, 'projects.db');
  db = openDb(dbPath);
  const config: Config = {
    port: 7317,
    bind: '127.0.0.1',
    dbPath,
    dataDir: dir,
    allowedOrigins: [],
    printToken: false,
    claudeProjectsDir: join(dir, 'claude', 'projects'),
    claudeSessionsDir: join(dir, 'claude', 'sessions'),
    vapidContact: 'mailto:test@localhost',
  };
  bundle = buildApp(config, db, '0.0.0-test');
  const t = bundle.auth.ensureBootstrapToken();
  if (!t) throw new Error('bootstrap token attendu sur une base vierge');
  token = t;
});

afterAll(async () => {
  await bundle.app.close();
  db.close();
  rmSync(dir, { recursive: true, force: true });
});

describe('POST /api/v1/projects', () => {
  it('crée le dossier et lance une session (sans git)', async () => {
    const res = await bundle.app.inject({ method: 'POST', url: '/api/v1/projects', headers: auth(), payload: { root, name: 'plain', command: 'bash' } });
    expect(res.statusCode).toBe(201);
    const body = res.json() as CreateProjectResponse;
    expect(body.path).toBe(join(root, 'plain'));
    expect(body.gitInitialized).toBe(false);
    expect(existsSync(join(root, 'plain'))).toBe(true);
    expect(existsSync(join(root, 'plain', '.git'))).toBe(false);
    expect(body.session.cwd).toBe(join(root, 'plain'));
  });

  it('gitInit:true crée un dépôt git', async () => {
    const res = await bundle.app.inject({ method: 'POST', url: '/api/v1/projects', headers: auth(), payload: { root, name: 'withgit', gitInit: true, command: 'bash' } });
    expect(res.statusCode).toBe(201);
    const body = res.json() as CreateProjectResponse;
    expect(body.gitInitialized).toBe(true);
    expect(existsSync(join(root, 'withgit', '.git'))).toBe(true);
  });

  it('nom avec `..` ou séparateur → 400 (anti-traversal)', async () => {
    for (const name of ['..', 'a/b', '../evil']) {
      const res = await bundle.app.inject({ method: 'POST', url: '/api/v1/projects', headers: auth(), payload: { root, name, command: 'bash' } });
      expect(res.statusCode).toBe(400);
    }
  });

  it('dossier déjà existant → 409 PROJECT_EXISTS', async () => {
    const res = await bundle.app.inject({ method: 'POST', url: '/api/v1/projects', headers: auth(), payload: { root, name: 'plain', command: 'bash' } });
    expect(res.statusCode).toBe(409);
    expect(res.json()).toMatchObject({ error: { code: 'PROJECT_EXISTS' } });
  });

  it('racine inexistante → 404', async () => {
    const res = await bundle.app.inject({ method: 'POST', url: '/api/v1/projects', headers: auth(), payload: { root: join(root, 'nope-xyz'), name: 'x', command: 'bash' } });
    expect(res.statusCode).toBe(404);
  });

  it('root relatif → 400', async () => {
    const res = await bundle.app.inject({ method: 'POST', url: '/api/v1/projects', headers: auth(), payload: { root: 'relatif/x', name: 'x', command: 'bash' } });
    expect(res.statusCode).toBe(400);
  });

  it('sans authentification → 401', async () => {
    const res = await bundle.app.inject({ method: 'POST', url: '/api/v1/projects', payload: { root, name: 'noauth', command: 'bash' } });
    expect(res.statusCode).toBe(401);
  });
});
