/**
 * Crawlie blog backend — Cloudflare Worker + KV
 * ------------------------------------------------
 * A minimal real CRUD API for blog posts, so the blog admin panel can create,
 * edit, and delete posts that are actually visible to every visitor — not just
 * saved in your own browser.
 *
 * DEPLOY (about 5 minutes, free):
 *   1. Go to https://dash.cloudflare.com -> Workers & Pages -> Create -> Worker.
 *   2. Delete the sample code and paste this entire file in.
 *   3. Go to Settings -> Variables -> KV Namespace Bindings -> Add binding.
 *      Create a new KV namespace (e.g. "crawlie-blog") and bind it to the
 *      variable name BLOG_POSTS (must match exactly).
 *   4. Go to Settings -> Variables -> Environment Variables -> Add variable.
 *      Name: ADMIN_TOKEN. Value: any long random password you make up — this
 *      is what protects create/edit/delete. Click "Encrypt" so it's stored as
 *      a secret. Remember this value, you'll need to paste it into the admin
 *      panel.
 *   5. Click Deploy. You'll get a URL like https://crawlie-blog.<you>.workers.dev
 *   6. Open blog/admin.html on your site, paste that URL and your ADMIN_TOKEN
 *      into the settings panel, and save.
 *
 * Free tier: Workers KV free tier easily covers a personal/small business blog.
 *
 * API:
 *   GET    /posts            -> list all published posts (public)
 *   GET    /posts?all=1      -> list ALL posts including drafts (requires auth)
 *   GET    /posts/:slug      -> get one post by slug (public)
 *   POST   /posts            -> create a post (requires auth), body: {slug, title, excerpt, content, published, date}
 *   PUT    /posts/:slug      -> update a post (requires auth), same body shape
 *   DELETE /posts/:slug      -> delete a post (requires auth)
 */

const CORS_HEADERS = {
  'Access-Control-Allow-Origin': '*',
  'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',
  'Access-Control-Allow-Headers': 'Content-Type, Authorization',
};

function json(data, status) {
  return new Response(JSON.stringify(data), {
    status: status || 200,
    headers: { ...CORS_HEADERS, 'Content-Type': 'application/json' },
  });
}

function isAuthed(request, env) {
  const auth = request.headers.get('Authorization') || '';
  const token = auth.replace(/^Bearer\s+/i, '');
  return !!env.ADMIN_TOKEN && token === env.ADMIN_TOKEN;
}

async function getIndex(env) {
  const raw = await env.BLOG_POSTS.get('index');
  return raw ? JSON.parse(raw) : [];
}
async function setIndex(env, slugs) {
  await env.BLOG_POSTS.put('index', JSON.stringify(slugs));
}

export default {
  async fetch(request, env) {
    if (request.method === 'OPTIONS') return new Response(null, { headers: CORS_HEADERS });

    const url = new URL(request.url);
    const parts = url.pathname.split('/').filter(Boolean); // ["posts"] or ["posts", "slug"]

    if (parts[0] !== 'posts') return json({ error: 'Not found' }, 404);

    try {
      // GET /posts  or  GET /posts?all=1
      if (request.method === 'GET' && parts.length === 1) {
        const slugs = await getIndex(env);
        const wantAll = url.searchParams.get('all') === '1';
        if (wantAll && !isAuthed(request, env)) return json({ error: 'Unauthorized' }, 401);

        const posts = [];
        for (const slug of slugs) {
          const raw = await env.BLOG_POSTS.get('post:' + slug);
          if (!raw) continue;
          const post = JSON.parse(raw);
          if (wantAll || post.published) posts.push(post);
        }
        posts.sort((a, b) => (b.date || '').localeCompare(a.date || ''));
        return json({ posts });
      }

      // GET /posts/:slug
      if (request.method === 'GET' && parts.length === 2) {
        const raw = await env.BLOG_POSTS.get('post:' + parts[1]);
        if (!raw) return json({ error: 'Not found' }, 404);
        const post = JSON.parse(raw);
        if (!post.published && !isAuthed(request, env)) return json({ error: 'Not found' }, 404);
        return json({ post });
      }

      // Everything past this point mutates data and requires auth.
      if (!isAuthed(request, env)) return json({ error: 'Unauthorized — check your ADMIN_TOKEN' }, 401);

      // POST /posts — create
      if (request.method === 'POST' && parts.length === 1) {
        const body = await request.json();
        if (!body.slug || !body.title) return json({ error: 'slug and title are required' }, 400);
        const existing = await env.BLOG_POSTS.get('post:' + body.slug);
        if (existing) return json({ error: 'A post with this slug already exists' }, 409);

        const post = {
          slug: body.slug, title: body.title, excerpt: body.excerpt || '',
          content: body.content || '', published: !!body.published,
          date: body.date || new Date().toISOString().slice(0, 10),
        };
        await env.BLOG_POSTS.put('post:' + post.slug, JSON.stringify(post));
        const slugs = await getIndex(env);
        if (!slugs.includes(post.slug)) { slugs.push(post.slug); await setIndex(env, slugs); }
        return json({ post }, 201);
      }

      // PUT /posts/:slug — update
      if (request.method === 'PUT' && parts.length === 2) {
        const body = await request.json();
        const existingRaw = await env.BLOG_POSTS.get('post:' + parts[1]);
        if (!existingRaw) return json({ error: 'Not found' }, 404);
        const existing = JSON.parse(existingRaw);
        const updated = {
          slug: parts[1], title: body.title ?? existing.title,
          excerpt: body.excerpt ?? existing.excerpt, content: body.content ?? existing.content,
          published: body.published ?? existing.published, date: body.date ?? existing.date,
        };
        await env.BLOG_POSTS.put('post:' + parts[1], JSON.stringify(updated));
        return json({ post: updated });
      }

      // DELETE /posts/:slug
      if (request.method === 'DELETE' && parts.length === 2) {
        await env.BLOG_POSTS.delete('post:' + parts[1]);
        const slugs = (await getIndex(env)).filter((s) => s !== parts[1]);
        await setIndex(env, slugs);
        return json({ ok: true });
      }

      return json({ error: 'Method not allowed' }, 405);
    } catch (e) {
      return json({ error: 'Server error: ' + (e && e.message ? e.message : 'unknown') }, 500);
    }
  },
};

