// server.js — zero-dependency HTTP server for the todo-cli web UI.
// Uses only Node.js built-in modules. Serves the REST API and static web files.

import http from "node:http";
import fs from "node:fs";
import path from "node:path";
import { fileURLToPath } from "node:url";
import { createStore } from "./store.js";
import {
  addTodo,
  editTodo,
  deleteTodo,
  toggleTodo,
  completeTodo,
  completeAll,
  listTodos,
  clearTodos,
  searchTodos,
  getStats,
  archiveCompleted,
  exportTodos,
  migrateAll,
  getTodo,
  addSubtask,
  toggleSubtask,
  deleteSubtask,
  editSubtask,
  setRecurring,
  removeRecurring,
  addDependency,
  removeDependency,
  startTimer,
  stopTimer,
  logTime,
  getActiveTimer,
  togglePin,
  duplicateTodo,
  bulkComplete,
  bulkDelete,
  bulkPriority,
  bulkCategory,
  bulkAssign,
  reorderTodo,
} from "./todo.js";

const __dirname = path.dirname(fileURLToPath(import.meta.url));
const WEB_DIR = path.join(__dirname, "..", "web");
const store = createStore();
let todos = migrateAll(store.load());

function save() {
  store.save(todos);
}

// ─── HTTP helpers ──────────────────────────────────────────────────────────

function sendJSON(res, data, status = 200) {
  res.writeHead(status, {
    "Content-Type": "application/json; charset=utf-8",
    "Access-Control-Allow-Origin": "*",
    "Access-Control-Allow-Methods": "GET, POST, PUT, PATCH, DELETE, OPTIONS",
    "Access-Control-Allow-Headers": "Content-Type",
  });
  res.end(JSON.stringify(data));
}

function sendStatic(res, filePath, contentType) {
  try {
    const data = fs.readFileSync(filePath);
    res.writeHead(200, { "Content-Type": contentType });
    res.end(data);
  } catch {
    sendJSON(res, { error: "Not found" }, 404);
  }
}

function readBody(req) {
  return new Promise((resolve) => {
    let body = "";
    req.on("data", (chunk) => (body += chunk));
    req.on("end", () => {
      try {
        resolve(body ? JSON.parse(body) : {});
      } catch {
        resolve({});
      }
    });
  });
}

const MIME = {
  ".html": "text/html; charset=utf-8",
  ".css": "text/css; charset=utf-8",
  ".js": "application/javascript; charset=utf-8",
  ".json": "application/json; charset=utf-8",
  ".svg": "image/svg+xml",
  ".png": "image/png",
  ".ico": "image/x-icon",
  ".woff2": "font/woff2",
};

// ─── API routes ────────────────────────────────────────────────────────────

async function handleAPI(req, res, pathname, method) {
  const parts = pathname.replace(/^\/api\//, "").split("/").filter(Boolean);
  const resource = parts[0];
  const id = parts[1] ? Number(parts[1]) : null;
  const sub = parts[2]; // sub-resource
  const subId = parts[3] ? Number(parts[3]) : null;
  const body = method === "POST" || method === "PUT" || method === "PATCH" ? await readBody(req) : {};

  try {
    // GET /api/todos
    if (resource === "todos" && method === "GET" && id === null) {
      const result = listTodos(todos, {
        status: req.url.searchParams.get("status") || undefined,
        category: req.url.searchParams.get("category") || undefined,
        tag: req.url.searchParams.get("tag") || undefined,
        priority: req.url.searchParams.get("priority") || undefined,
        assignee: req.url.searchParams.get("assignee") || undefined,
        sort: req.url.searchParams.get("sort") || undefined,
        overdue: req.url.searchParams.get("overdue") === "true",
        dueToday: req.url.searchParams.get("dueToday") === "true",
        pinnedFirst: true,
      });
      return sendJSON(res, result);
    }

    // POST /api/todos
    if (resource === "todos" && method === "POST" && id === null) {
      const todo = addTodo(todos, body);
      save();
      return sendJSON(res, todo, 201);
    }

    // GET /api/todos/:id
    if (resource === "todos" && method === "GET" && id !== null && sub === undefined) {
      const todo = getTodo(todos, id);
      if (!todo) return sendJSON(res, { error: "Not found" }, 404);
      return sendJSON(res, todo);
    }

    // PUT/PATCH /api/todos/:id
    if (resource === "todos" && (method === "PUT" || method === "PATCH") && id !== null && sub === undefined) {
      const todo = editTodo(todos, id, body);
      if (!todo) return sendJSON(res, { error: "Not found" }, 404);
      save();
      return sendJSON(res, todo);
    }

    // DELETE /api/todos/:id
    if (resource === "todos" && method === "DELETE" && id !== null && sub === undefined) {
      const ok = deleteTodo(todos, id);
      if (!ok) return sendJSON(res, { error: "Not found" }, 404);
      save();
      return sendJSON(res, { ok: true });
    }

    // POST /api/todos/:id/toggle
    if (resource === "todos" && method === "POST" && id !== null && sub === "toggle") {
      const todo = toggleTodo(todos, id);
      if (!todo) return sendJSON(res, { error: "Not found" }, 404);
      save();
      return sendJSON(res, todo);
    }

    // POST /api/todos/:id/complete
    if (resource === "todos" && method === "POST" && id !== null && sub === "complete") {
      const todo = completeTodo(todos, id);
      if (!todo) return sendJSON(res, { error: "Not found" }, 404);
      save();
      return sendJSON(res, todo);
    }

    // POST /api/todos/:id/pin
    if (resource === "todos" && method === "POST" && id !== null && sub === "pin") {
      const todo = togglePin(todos, id);
      if (!todo) return sendJSON(res, { error: "Not found" }, 404);
      save();
      return sendJSON(res, todo);
    }

    // POST /api/todos/:id/duplicate
    if (resource === "todos" && method === "POST" && id !== null && sub === "duplicate") {
      const todo = duplicateTodo(todos, id);
      if (!todo) return sendJSON(res, { error: "Not found" }, 404);
      save();
      return sendJSON(res, todo, 201);
    }

    // POST /api/todos/:id/reorder
    if (resource === "todos" && method === "POST" && id !== null && sub === "reorder") {
      const todo = reorderTodo(todos, id, body.order);
      if (!todo) return sendJSON(res, { error: "Not found" }, 404);
      save();
      return sendJSON(res, todo);
    }

    // ── Subtasks ──
    // POST /api/todos/:id/subtasks
    if (resource === "todos" && method === "POST" && id !== null && sub === "subtasks" && subId === null) {
      const st = addSubtask(todos, id, body.title);
      if (!st) return sendJSON(res, { error: "Not found" }, 404);
      save();
      return sendJSON(res, st, 201);
    }

    // POST /api/todos/:id/subtasks/:sid/toggle
    if (resource === "todos" && method === "POST" && id !== null && sub === "subtasks" && subId !== null && parts[4] === "toggle") {
      const st = toggleSubtask(todos, id, subId);
      if (!st) return sendJSON(res, { error: "Not found" }, 404);
      save();
      return sendJSON(res, st);
    }

    // PATCH /api/todos/:id/subtasks/:sid
    if (resource === "todos" && method === "PATCH" && id !== null && sub === "subtasks" && subId !== null) {
      const st = editSubtask(todos, id, subId, body.title);
      if (!st) return sendJSON(res, { error: "Not found" }, 404);
      save();
      return sendJSON(res, st);
    }

    // DELETE /api/todos/:id/subtasks/:sid
    if (resource === "todos" && method === "DELETE" && id !== null && sub === "subtasks" && subId !== null) {
      const ok = deleteSubtask(todos, id, subId);
      if (!ok) return sendJSON(res, { error: "Not found" }, 404);
      save();
      return sendJSON(res, { ok: true });
    }

    // ── Recurring ──
    // POST /api/todos/:id/recurring
    if (resource === "todos" && method === "POST" && id !== null && sub === "recurring") {
      const todo = body.freq ? setRecurring(todos, id, body.freq, body.interval) : removeRecurring(todos, id);
      if (!todo) return sendJSON(res, { error: "Not found" }, 404);
      save();
      return sendJSON(res, todo);
    }

    // ── Dependencies ──
    // POST /api/todos/:id/dependencies
    if (resource === "todos" && method === "POST" && id !== null && sub === "dependencies") {
      const todo = addDependency(todos, id, body.depId);
      if (!todo) return sendJSON(res, { error: "Not found" }, 404);
      save();
      return sendJSON(res, todo);
    }

    // DELETE /api/todos/:id/dependencies/:depId
    if (resource === "todos" && method === "DELETE" && id !== null && sub === "dependencies" && subId !== null) {
      const todo = removeDependency(todos, id, subId);
      if (!todo) return sendJSON(res, { error: "Not found" }, 404);
      save();
      return sendJSON(res, todo);
    }

    // ── Timer ──
    // POST /api/todos/:id/timer/start
    if (resource === "todos" && method === "POST" && id !== null && sub === "timer" && parts[3] === "start") {
      const todo = startTimer(todos, id);
      if (!todo) return sendJSON(res, { error: "Not found" }, 404);
      save();
      return sendJSON(res, todo);
    }

    // POST /api/todos/:id/timer/stop
    if (resource === "todos" && method === "POST" && id !== null && sub === "timer" && parts[3] === "stop") {
      const todo = stopTimer(todos, id);
      if (!todo) return sendJSON(res, { error: "Not found" }, 404);
      save();
      return sendJSON(res, todo);
    }

    // POST /api/todos/:id/timer/log
    if (resource === "todos" && method === "POST" && id !== null && sub === "timer" && parts[3] === "log") {
      const todo = logTime(todos, id, body.minutes);
      if (!todo) return sendJSON(res, { error: "Not found" }, 404);
      save();
      return sendJSON(res, todo);
    }

    // ── Stats ──
    if (resource === "stats" && method === "GET") {
      return sendJSON(res, getStats(todos));
    }

    // ── Search ──
    if (resource === "search" && method === "GET") {
      const q = req.url.searchParams.get("q") || "";
      return sendJSON(res, searchTodos(todos, q));
    }

    // ── Timer active ──
    if (resource === "timer" && method === "GET") {
      return sendJSON(res, getActiveTimer(todos));
    }

    // ── Bulk ──
    if (resource === "bulk" && method === "POST") {
      const { action, ids } = body;
      let result;
      switch (action) {
        case "complete": result = { count: bulkComplete(todos, ids) }; break;
        case "delete": result = { count: bulkDelete(todos, ids) }; break;
        case "priority": result = { count: bulkPriority(todos, ids, body.priority) }; break;
        case "category": result = { count: bulkCategory(todos, ids, body.category) }; break;
        case "assign": result = { count: bulkAssign(todos, ids, body.assignee) }; break;
        default: return sendJSON(res, { error: "Unknown bulk action" }, 400);
      }
      save();
      return sendJSON(res, result);
    }

    // ── Complete all ──
    if (resource === "complete-all" && method === "POST") {
      const n = completeAll(todos);
      save();
      return sendJSON(res, { count: n });
    }

    // ── Archive ──
    if (resource === "archive" && method === "POST") {
      const n = archiveCompleted(todos);
      save();
      return sendJSON(res, { count: n });
    }

    // ── Clear ──
    if (resource === "clear" && method === "POST") {
      const n = clearTodos(todos);
      save();
      return sendJSON(res, { count: n });
    }

    // ── Export ──
    if (resource === "export" && method === "GET") {
      const format = req.url.searchParams.get("format") || "json";
      return sendJSON(res, { data: exportTodos(todos, format) });
    }

    return sendJSON(res, { error: "Not found" }, 404);
  } catch (err) {
    return sendJSON(res, { error: err.message }, 400);
  }
}

// ─── Main server ───────────────────────────────────────────────────────────

export function startServer(port = 3456) {
  const server = http.createServer(async (req, res) => {
    // Parse URL
    req.url = new URL(req.url, `http://localhost:${port}`);
    const pathname = req.url.pathname;
    const method = req.method;

    // CORS preflight
    if (method === "OPTIONS") {
      res.writeHead(204, {
        "Access-Control-Allow-Origin": "*",
        "Access-Control-Allow-Methods": "GET, POST, PUT, PATCH, DELETE, OPTIONS",
        "Access-Control-Allow-Headers": "Content-Type",
      });
      return res.end();
    }

    // API routes
    if (pathname.startsWith("/api/")) {
      return handleAPI(req, res, pathname, method);
    }

    // Static file serving
    let filePath = pathname === "/" ? "/index.html" : pathname;
    const fullPath = path.join(WEB_DIR, filePath);

    // Prevent path traversal
    if (!fullPath.startsWith(WEB_DIR)) {
      return sendJSON(res, { error: "Forbidden" }, 403);
    }

    // Serve directory with index.html
    if (fs.existsSync(fullPath) && fs.statSync(fullPath).isDirectory()) {
      const indexPath = path.join(fullPath, "index.html");
      if (fs.existsSync(indexPath)) {
        return sendStatic(res, indexPath, MIME[".html"]);
      }
    }

    // Serve file
    if (fs.existsSync(fullPath) && fs.statSync(fullPath).isFile()) {
      const ext = path.extname(fullPath);
      const contentType = MIME[ext] || "application/octet-stream";
      return sendStatic(res, fullPath, contentType);
    }

    // SPA fallback: serve index.html for unknown routes
    const indexFallback = path.join(WEB_DIR, "index.html");
    if (fs.existsSync(indexFallback)) {
      return sendStatic(res, indexFallback, MIME[".html"]);
    }

    return sendJSON(res, { error: "Not found" }, 404);
  });

  server.listen(port, () => {
    console.log(`\n  ╔══════════════════════════════════════════════════╗`);
    console.log(`  ║  ${"todo-cli Web UI".padEnd(46)}  ║`);
    console.log(`  ║${" ".repeat(46)}  ║`);
    console.log(`  ║  ${(`➜  http://localhost:${port}`).padEnd(46)}  ║`);
    console.log(`  ║${" ".repeat(46)}  ║`);
    console.log(`  ║  ${"Press Ctrl+C to stop".padEnd(46)}  ║`);
    console.log(`  ╚══════════════════════════════════════════════════╝\n`);
  });

  return server;
}

// Allow running directly: node src/server.js [port]
if (import.meta.url === `file://${process.argv[1]}`) {
  const port = Number(process.argv[2]) || 3456;
  startServer(port);
}