import { Hono } from "hono";
import { sign } from "hono/jwt";
import { nanoid } from "nanoid";
import { apiError } from "../lib/errors";
import type { AppEnv } from "../types";

export const TOKEN_TTL_SECONDS = 30 * 24 * 3600;

interface LicenseRow {
  license_key: string;
  storage_bytes_limit: number;
  retention_days: number;
  max_sites: number;
}

export const sites = new Hono<AppEnv>();

sites.post("/activate", async (c) => {
  const body = (await c.req.json().catch(() => null)) as
    | { license_key?: unknown; site_url?: unknown }
    | null;
  if (!body || typeof body.license_key !== "string" || typeof body.site_url !== "string") {
    return apiError(c, 400, "invalid_request", "license_key and site_url are required");
  }

  let siteUrl: string;
  try {
    const u = new URL(body.site_url);
    if (u.protocol !== "https:" && u.protocol !== "http:") throw new Error("protocol");
    siteUrl = u.origin + u.pathname.replace(/\/+$/, "");
  } catch {
    return apiError(c, 400, "invalid_request", "site_url must be a valid http(s) URL");
  }

  const license = await c.env.DB.prepare(
    "SELECT license_key, storage_bytes_limit, retention_days, max_sites FROM licenses WHERE license_key = ? AND status = 'active'",
  )
    .bind(body.license_key)
    .first<LicenseRow>();
  if (!license) return apiError(c, 403, "invalid_license", "License not found or inactive");

  const existing = await c.env.DB.prepare(
    "SELECT id, token_version FROM sites WHERE license_key = ? AND site_url = ?",
  )
    .bind(license.license_key, siteUrl)
    .first<{ id: string; token_version: number }>();

  let siteId: string;
  let tokenVersion: number;

  if (existing) {
    siteId = existing.id;
    tokenVersion = existing.token_version + 1;
    await c.env.DB.prepare("UPDATE sites SET token_version = ? WHERE id = ?")
      .bind(tokenVersion, siteId)
      .run();
  } else {
    const count = await c.env.DB.prepare(
      "SELECT COUNT(*) AS n FROM sites WHERE license_key = ?",
    )
      .bind(license.license_key)
      .first<{ n: number }>();
    if ((count?.n ?? 0) >= license.max_sites) {
      return apiError(c, 403, "site_limit_reached", "License site limit reached");
    }
    const bucket = await c.env.DB.prepare(
      "SELECT id FROM buckets ORDER BY site_count ASC LIMIT 1",
    ).first<{ id: number }>();
    if (!bucket) return apiError(c, 500, "no_bucket", "No storage bucket available");

    siteId = nanoid();
    tokenVersion = 1;
    await c.env.DB.batch([
      c.env.DB.prepare(
        "INSERT INTO sites (id, license_key, site_url, bucket_id, token_version, storage_bytes_used) VALUES (?, ?, ?, ?, 1, 0)",
      ).bind(siteId, license.license_key, siteUrl, bucket.id),
      c.env.DB.prepare("UPDATE buckets SET site_count = site_count + 1 WHERE id = ?").bind(
        bucket.id,
      ),
    ]);
  }

  const token = await sign(
    {
      sub: siteId,
      ver: tokenVersion,
      exp: Math.floor(Date.now() / 1000) + TOKEN_TTL_SECONDS,
    },
    c.env.JWT_SECRET,
  );

  return c.json(
    {
      site_id: siteId,
      site_token: token,
      plan: {
        storage_bytes_limit: license.storage_bytes_limit,
        retention_days: license.retention_days,
      },
    },
    201,
  );
});
