import "dotenv/config";
import path from "node:path";
import { fileURLToPath } from "node:url";
import { existsSync } from "node:fs";
import Fastify from "fastify";
import cors from "@fastify/cors";
import staticPlugin from "@fastify/static";
import { ensureSchema, closeDb, getDbPath } from "./db.js";
import { geocode } from "./geocoder.js";
import { getActiveProviderInfo } from "./ai/index.js";
import { registerDocs, routeSchemas } from "./openapi.js";

const __dirname = path.dirname(fileURLToPath(import.meta.url));
const ROOT = path.resolve(__dirname, "..");
const UI_DIST = path.resolve(ROOT, "dist");

const port = Number(process.env.PORT || 3030);
const host = process.env.HOST || "127.0.0.1";

if (!existsSync(getDbPath())) {
  console.error(`
Database not found at ${getDbPath()}.
Run:  npm run gazetteer:load
`);
  process.exit(1);
}

ensureSchema();

const app = Fastify({
  logger: { level: process.env.LOG_LEVEL || "info" },
  // OpenAPI uses `example` in schemas; teach Ajv (querystring validation) to
  // treat it as a known no-op keyword instead of erroring in strict mode.
  ajv: { customOptions: { keywords: [{ keyword: "example" }] } },
});

await app.register(cors, { origin: true });

// Register Swagger before the routes so it can read their schemas.
// Interactive docs are served at /docs, the raw spec at /docs/json.
await registerDocs(app);

app.get("/api/health", { schema: routeSchemas.health }, async () => ({
  ok: true,
  service: "united-states-geocoder",
  dataset: "US Census 2024 Gazetteer Places",
  ai: getActiveProviderInfo(),
}));

const truthy = (v) => ["1", "true", "yes", "on"].includes(String(v ?? "").toLowerCase());

app.get("/api/geocode", { schema: routeSchemas.geocode }, async (req, reply) => {
  const city = String(req.query?.city || "").trim();
  const state = String(req.query?.state || "").trim();
  const useAi = truthy(req.query?.useAi);
  const aiProvider = req.query?.provider ? String(req.query.provider).toLowerCase() : null;
  if (!city || !state) {
    return reply.status(400).send({ error: "Both `city` and `state` query params are required." });
  }
  try {
    const result = await geocode({ city, state, useAi, aiProvider });
    return reply.send(result);
  } catch (err) {
    req.log.warn({ err }, "geocode failed");
    return reply.status(500).send({ error: err.message || "Geocoding failed." });
  }
});

if (existsSync(UI_DIST)) {
  await app.register(staticPlugin, { root: UI_DIST, prefix: "/" });
  app.setNotFoundHandler((req, reply) => {
    if (req.raw.url?.startsWith("/api/")) return reply.status(404).send({ error: "Not found" });
    return reply.sendFile("index.html");
  });
}

for (const signal of ["SIGINT", "SIGTERM"]) {
  process.on(signal, async () => {
    try { await app.close(); closeDb(); } finally { process.exit(0); }
  });
}

await app.listen({ port, host });
const ai = getActiveProviderInfo();
app.log.info(
  ai.available
    ? `AI fallback: enabled (${ai.provider} · ${ai.model})`
    : `AI fallback: disabled (${ai.reason})`,
);
