<?php
/**
 * Mini-projet 08 — API REST Gestionnaire de Tâches
 * Démarrage : php -S localhost:8000 puis tester avec curl ou ?ui=1 pour l'interface web
 */

// ── Interface web (optionnelle) ────────────────────────────
// Accéder à ?ui=1 affiche une interface de test HTML
if (isset($_GET['ui'])) {
    include __DIR__ . '/ui.php'; // voir ci-dessous dans le même fichier
    exit;
}

// ── CORS ───────────────────────────────────────────────────
$allowedOrigins = ['http://localhost:3000', 'http://localhost:5173'];
$origin = $_SERVER['HTTP_ORIGIN'] ?? '';
if (in_array($origin, $allowedOrigins, true)) {
    header("Access-Control-Allow-Origin: $origin");
    header('Access-Control-Allow-Credentials: true');
}
header('Access-Control-Allow-Methods: GET, POST, PUT, PATCH, DELETE, OPTIONS');
header('Access-Control-Allow-Headers: Content-Type, Authorization');
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') { http_response_code(204); exit; }

// ── Headers de réponse ─────────────────────────────────────
header('Content-Type: application/json; charset=utf-8');
header('X-Content-Type-Options: nosniff');

// ── Handler global d'erreurs ───────────────────────────────
set_exception_handler(function (Throwable $e): never {
    error_log($e->getMessage());
    respond(500, ['error' => 'Erreur interne du serveur']);
});

// ── DB SQLite ──────────────────────────────────────────────
$dbPath = sys_get_temp_dir() . '/php_tasks_api.sqlite';
$pdo    = new PDO("sqlite:$dbPath", null, null, [
    PDO::ATTR_ERRMODE            => PDO::ERRMODE_EXCEPTION,
    PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
]);
$pdo->exec('CREATE TABLE IF NOT EXISTS tasks (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    titre TEXT NOT NULL,
    description TEXT DEFAULT "",
    statut TEXT DEFAULT "todo",
    priorite TEXT DEFAULT "normale",
    user TEXT NOT NULL DEFAULT "system",
    created_at TEXT DEFAULT (datetime("now","localtime"))
)');

// ── Tokens d'authentification ──────────────────────────────
$TOKENS = ['tok-alice' => 'alice', 'tok-bob' => 'bob'];

// ── Helpers ────────────────────────────────────────────────
function respond(int $code, mixed $data): never {
    http_response_code($code);
    echo json_encode($data, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT);
    exit;
}

function getJsonBody(): array {
    $raw = file_get_contents('php://input');
    if (!$raw) return [];
    $data = json_decode($raw, true);
    if (json_last_error() !== JSON_ERROR_NONE) {
        respond(400, ['error' => 'JSON invalide : ' . json_last_error_msg()]);
    }
    return $data;
}

function requireAuth(array $tokens): string {
    $headers = function_exists('getallheaders') ? getallheaders() : [];
    $auth    = $headers['Authorization'] ?? $_SERVER['HTTP_AUTHORIZATION'] ?? '';
    if (!preg_match('/^Bearer\s+(.+)$/i', $auth, $m)) {
        respond(401, ['error' => 'Token Bearer requis', 'hint' => 'Header: Authorization: Bearer tok-alice']);
    }
    foreach ($tokens as $tok => $user) {
        if (hash_equals($tok, $m[1])) return $user; // comparaison en temps constant
    }
    respond(401, ['error' => 'Token invalide']);
}

function paginate(array $items, int $page, int $perPage): array {
    $total = count($items);
    $pages = (int)ceil($total / $perPage);
    return [
        'data'  => array_slice($items, ($page - 1) * $perPage, $perPage),
        'meta'  => ['total' => $total, 'page' => $page, 'per_page' => $perPage, 'last_page' => $pages],
        'links' => [
            'prev' => $page > 1      ? "?page=" . ($page - 1) : null,
            'next' => $page < $pages ? "?page=" . ($page + 1) : null,
        ],
    ];
}

// ── Routage ────────────────────────────────────────────────
$method   = $_SERVER['REQUEST_METHOD'];
$path     = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);
$segments = array_values(array_filter(explode('/', trim($path, '/'))));
// ['api', 'tasks'] ou ['api', 'tasks', '42'] ou ['api', 'me']

$res  = $segments[1] ?? '';  // resource
$id   = isset($segments[2]) ? (int)$segments[2] : 0;

if (($segments[0] ?? '') !== 'api') {
    respond(404, ['error' => 'API prefix /api required']);
}

// ── Route: GET /api/me ─────────────────────────────────────
if ($res === 'me' && $method === 'GET') {
    $user = requireAuth($TOKENS);
    $count = $pdo->prepare('SELECT COUNT(*) FROM tasks WHERE user = :u');
    $count->execute([':u' => $user]);
    respond(200, ['user' => $user, 'tasks_count' => (int)$count->fetchColumn()]);
}

if ($res !== 'tasks') respond(404, ['error' => "Resource '$res' introuvable"]);

// ── Route: GET /api/tasks ──────────────────────────────────
if ($method === 'GET' && !$id) {
    $page    = max(1, (int)($_GET['page'] ?? 1));
    $perPage = min(50, max(1, (int)($_GET['per_page'] ?? 10)));
    $statut  = $_GET['statut'] ?? '';
    if ($statut) {
        $stmt = $pdo->prepare('SELECT * FROM tasks WHERE statut = :s ORDER BY id DESC');
        $stmt->execute([':s' => $statut]);
    } else {
        $stmt = $pdo->query('SELECT * FROM tasks ORDER BY id DESC');
    }
    respond(200, paginate($stmt->fetchAll(), $page, $perPage));
}

// ── Route: GET /api/tasks/{id} ─────────────────────────────
if ($method === 'GET' && $id) {
    $stmt = $pdo->prepare('SELECT * FROM tasks WHERE id = :id');
    $stmt->execute([':id' => $id]);
    $task = $stmt->fetch();
    if (!$task) respond(404, ['error' => "Tâche #$id introuvable"]);
    respond(200, ['data' => $task]);
}

// ── Route: POST /api/tasks ─────────────────────────────────
if ($method === 'POST' && !$id) {
    $user = requireAuth($TOKENS);
    $body = getJsonBody();

    $titre = trim($body['titre'] ?? '');
    $errors = [];
    if (strlen($titre) < 2 || strlen($titre) > 200) $errors[] = 'titre : 2 à 200 caractères';

    $statuts = ['todo', 'doing', 'done'];
    $prios   = ['basse', 'normale', 'haute', 'urgente'];
    $statut  = $body['statut'] ?? 'todo';
    $prio    = $body['priorite'] ?? 'normale';
    if (!in_array($statut, $statuts)) $errors[] = 'statut invalide (todo|doing|done)';
    if (!in_array($prio, $prios))     $errors[] = 'priorite invalide (basse|normale|haute|urgente)';

    if ($errors) respond(422, ['errors' => $errors]);

    $stmt = $pdo->prepare('INSERT INTO tasks (titre, description, statut, priorite, user) VALUES (:t,:d,:s,:p,:u)');
    $stmt->execute([
        ':t' => $titre,
        ':d' => trim($body['description'] ?? ''),
        ':s' => $statut,
        ':p' => $prio,
        ':u' => $user,
    ]);
    $newId = (int)$pdo->lastInsertId();
    header("Location: /api/tasks/$newId"); // 201 inclut toujours Location
    respond(201, ['data' => ['id' => $newId, 'titre' => $titre, 'statut' => $statut, 'user' => $user]]);
}

// ── Route: PUT /api/tasks/{id} ─────────────────────────────
if ($method === 'PUT' && $id) {
    $user = requireAuth($TOKENS);
    $body = getJsonBody();

    $stmt = $pdo->prepare('SELECT * FROM tasks WHERE id = :id');
    $stmt->execute([':id' => $id]);
    if (!$stmt->fetch()) respond(404, ['error' => "Tâche #$id introuvable"]);

    $titre = trim($body['titre'] ?? '');
    if (strlen($titre) < 2) respond(422, ['errors' => ['titre requis (min 2 cars)']]);

    $stmt = $pdo->prepare('UPDATE tasks SET titre=:t, description=:d, statut=:s, priorite=:p WHERE id=:id');
    $stmt->execute([
        ':t' => $titre,
        ':d' => trim($body['description'] ?? ''),
        ':s' => $body['statut'] ?? 'todo',
        ':p' => $body['priorite'] ?? 'normale',
        ':id'=> $id,
    ]);
    $stmt2 = $pdo->prepare('SELECT * FROM tasks WHERE id = :id');
    $stmt2->execute([':id' => $id]);
    respond(200, ['data' => $stmt2->fetch()]);
}

// ── Route: PATCH /api/tasks/{id} ───────────────────────────
if ($method === 'PATCH' && $id) {
    requireAuth($TOKENS);
    $body = getJsonBody();

    $stmt = $pdo->prepare('SELECT * FROM tasks WHERE id = :id');
    $stmt->execute([':id' => $id]);
    $task = $stmt->fetch();
    if (!$task) respond(404, ['error' => "Tâche #$id introuvable"]);

    // PATCH = modifier seulement les champs envoyés (contrairement à PUT qui remplace tout)
    $merged = array_merge($task, array_intersect_key($body, $task));
    $stmt = $pdo->prepare('UPDATE tasks SET titre=:t, description=:d, statut=:s, priorite=:p WHERE id=:id');
    $stmt->execute([':t'=>$merged['titre'],':d'=>$merged['description'],
                    ':s'=>$merged['statut'],':p'=>$merged['priorite'],':id'=>$id]);
    $stmt2 = $pdo->prepare('SELECT * FROM tasks WHERE id = :id');
    $stmt2->execute([':id' => $id]);
    respond(200, ['data' => $stmt2->fetch()]);
}

// ── Route: DELETE /api/tasks/{id} ─────────────────────────
if ($method === 'DELETE' && $id) {
    requireAuth($TOKENS);
    $stmt = $pdo->prepare('DELETE FROM tasks WHERE id = :id');
    $stmt->execute([':id' => $id]);
    if ($stmt->rowCount() === 0) respond(404, ['error' => "Tâche #$id introuvable"]);
    http_response_code(204); // No Content — pas de body pour un DELETE réussi
    exit;
}

respond(405, ['error' => "Méthode '$method' non supportée sur cette route"]);

// ────────────────────────────────────────────────────────────
// ── Interface HTML (appelée quand ?ui=1) ─────────────────
// ────────────────────────────────────────────────────────────
function ui_page(): void { ?>
<!DOCTYPE html>
<html lang="fr">
<head>
  <meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>API Tasks — Interface</title>
  <style>
    *{margin:0;padding:0;box-sizing:border-box}
    body{background:#0a0e1a;color:#e6edf3;font-family:'Segoe UI',sans-serif;padding:1.5rem 1rem}
    .container{max-width:900px;margin:0 auto}
    h1{font-size:1.4rem;color:#3fb950;margin-bottom:.3rem}
    .subtitle{color:#6e7681;font-size:.82rem;margin-bottom:1.5rem}
    .grid{display:grid;grid-template-columns:1fr 1fr;gap:1rem}
    .panel{background:rgba(22,27,34,.8);border:1px solid rgba(255,255,255,.08);border-radius:12px;padding:1.2rem}
    h2{font-size:.92rem;color:#8b949e;margin-bottom:.8rem;font-weight:700}
    input,select,textarea{width:100%;background:rgba(255,255,255,.04);border:1px solid rgba(255,255,255,.1);border-radius:6px;color:#e6edf3;padding:.5rem .7rem;font-size:.82rem;font-family:inherit;outline:none;margin-bottom:.6rem}
    label{display:block;font-size:.72rem;color:#6e7681;margin-bottom:.2rem}
    .btn{padding:.5rem 1rem;background:#3fb950;color:#0d1117;border:none;border-radius:6px;font-weight:700;font-size:.82rem;cursor:pointer;transition:opacity .2s}
    .btn:hover{opacity:.8}.btn-red{background:#f85149;color:#fff}
    pre#output{background:#161b22;border:1px solid rgba(255,255,255,.06);border-radius:8px;padding:1rem;font-size:.78rem;color:#8b949e;min-height:120px;overflow:auto;white-space:pre-wrap;margin-top:1rem}
    .task-card{background:rgba(63,185,80,.05);border:1px solid rgba(63,185,80,.15);border-radius:8px;padding:.8rem;margin-bottom:.6rem}
    .task-title{font-weight:700;font-size:.88rem;margin-bottom:.2rem}
    .task-meta{font-size:.72rem;color:#6e7681}
    .badge{display:inline-block;padding:.1rem .4rem;border-radius:3px;font-size:.68rem;font-weight:700}
    .todo-b{background:rgba(139,148,158,.15);color:#8b949e}
    .doing-b{background:rgba(240,196,25,.15);color:#f0c419}
    .done-b{background:rgba(63,185,80,.15);color:#3fb950}
  </style>
</head>
<body>
<div class="container">
  <h1>✅ API Gestionnaire de Tâches</h1>
  <p class="subtitle">Interface de démonstration · <a href="." style="color:#3fb950">← Retour mini-projet</a></p>

  <div class="grid">
    <div>
      <div class="panel" style="margin-bottom:1rem">
        <h2>➕ Créer une tâche</h2>
        <label>Token d'auth</label>
        <select id="token"><option value="tok-alice">tok-alice (Alice)</option><option value="tok-bob">tok-bob (Bob)</option></select>
        <label>Titre *</label>
        <input type="text" id="new-titre" placeholder="Titre de la tâche">
        <label>Priorité</label>
        <select id="new-prio"><option value="normale">Normale</option><option value="basse">Basse</option><option value="haute">Haute</option><option value="urgente">Urgente</option></select>
        <button class="btn" onclick="createTask()">Créer</button>
      </div>

      <div class="panel">
        <h2>🔌 Requête manuelle</h2>
        <label>Méthode + URL</label>
        <div style="display:flex;gap:.4rem;margin-bottom:.6rem">
          <select id="req-method" style="width:90px;margin:0"><option>GET</option><option>POST</option><option>PUT</option><option>PATCH</option><option>DELETE</option></select>
          <input type="text" id="req-url" style="margin:0;flex:1" value="/api/tasks" placeholder="/api/tasks">
        </div>
        <label>Body JSON (optionnel)</label>
        <textarea id="req-body" rows="3" placeholder='{"titre":"..."}'></textarea>
        <button class="btn" onclick="sendRequest()">Envoyer</button>
        <pre id="output">// Résultat de la requête...</pre>
      </div>
    </div>

    <div class="panel">
      <h2>📋 Tâches <span id="tasks-count" style="color:#6e7681;font-weight:400"></span></h2>
      <div id="tasks-list"></div>
      <button class="btn" style="width:100%;margin-top:.5rem;background:rgba(255,255,255,.05);color:#8b949e" onclick="loadTasks()">↺ Actualiser</button>
    </div>
  </div>
</div>

<script>
const API = '';

async function req(method, url, body = null, token = null) {
  const headers = {'Content-Type': 'application/json'};
  if (token) headers['Authorization'] = 'Bearer ' + token;
  const opts = {method, headers};
  if (body) opts.body = JSON.stringify(body);
  const res = await fetch(API + url, opts);
  const text = await res.text();
  try { return {status: res.status, data: JSON.parse(text)}; }
  catch { return {status: res.status, data: text}; }
}

async function loadTasks() {
  const {status, data} = await req('GET', '/api/tasks?per_page=20');
  const list = document.getElementById('tasks-list');
  const cnt  = document.getElementById('tasks-count');
  if (status === 200 && data.data) {
    cnt.textContent = '(' + data.meta.total + ')';
    list.innerHTML = data.data.map(t => `
      <div class="task-card">
        <div class="task-title">${htmlEsc(t.titre)}</div>
        <div class="task-meta">
          <span class="badge ${t.statut}-b">${t.statut}</span>
          · ${htmlEsc(t.priorite)} · ${htmlEsc(t.user)}
          <button onclick="deleteTask(${t.id})" style="float:right;background:none;border:none;color:#f85149;cursor:pointer;font-size:.75rem">✕</button>
          <button onclick="doneTask(${t.id},${t.statut==='done'})" style="float:right;background:none;border:none;color:#3fb950;cursor:pointer;font-size:.75rem;margin-right:.3rem">${t.statut==='done'?'↩':'✓'}</button>
        </div>
      </div>`).join('') || '<p style="color:#6e7681;font-size:.85rem">Aucune tâche.</p>';
  }
}

async function createTask() {
  const titre = document.getElementById('new-titre').value.trim();
  const prio  = document.getElementById('new-prio').value;
  const token = document.getElementById('token').value;
  if (!titre) return alert('Titre requis');
  const {status, data} = await req('POST', '/api/tasks', {titre, priorite: prio}, token);
  document.getElementById('output').textContent = JSON.stringify({status, data}, null, 2);
  if (status === 201) { document.getElementById('new-titre').value = ''; loadTasks(); }
}

async function deleteTask(id) {
  const token = document.getElementById('token').value;
  const {status} = await req('DELETE', '/api/tasks/' + id, null, token);
  document.getElementById('output').textContent = 'DELETE /api/tasks/' + id + ' → ' + status;
  loadTasks();
}

async function doneTask(id, isDone) {
  const token = document.getElementById('token').value;
  const {status, data} = await req('PATCH', '/api/tasks/' + id, {statut: isDone ? 'todo' : 'done'}, token);
  document.getElementById('output').textContent = JSON.stringify({status, data}, null, 2);
  loadTasks();
}

async function sendRequest() {
  const method = document.getElementById('req-method').value;
  const url    = document.getElementById('req-url').value;
  const rawBody = document.getElementById('req-body').value.trim();
  const token  = document.getElementById('token').value;
  let body = null;
  if (rawBody) { try { body = JSON.parse(rawBody); } catch { alert('JSON invalide'); return; } }
  const {status, data} = await req(method, url, body, token);
  document.getElementById('output').textContent = '// HTTP ' + status + '\n' + JSON.stringify(data, null, 2);
}

function htmlEsc(s) {
  return String(s).replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/"/g,'&quot;');
}

loadTasks();
</script>
</body>
</html>
<?php } // end ui_page

// Inclure l'UI si demandé (depuis include ci-dessus en début de fichier)
if (isset($_GET['ui'])) { ui_page(); exit; }
