// src/index.ts
import express, { type Request, type Response, type NextFunction } from "express";
import cors from "cors";
import cron from "node-cron";
import { ENV } from "./env.js";
import { health } from "./routes/health.js";
import { install } from "./routes/install.js";
import { api } from "./routes/api.js";
import { webhooksHandler, WEBHOOK_PATH } from "./shopify/webhooks.js";
import { logger } from "./logger.js";
import { query } from "./db.js";
import { incrementalSync } from "./services/sync.js";
import path from "path";
const __dirname = path.resolve();

const app = express();
app.set("trust proxy", 1);

// ─────────────────────────────────────────────────────────────────────────────
// Security headers (CSP so the app can be framed by Shopify Admin / Store)
// ─────────────────────────────────────────────────────────────────────────────
app.use((_: Request, res: Response, next: NextFunction) => {
  res.setHeader(
    "Content-Security-Policy",
    "frame-ancestors https://admin.shopify.com https://*.myshopify.com;"
  );
  next();
});

// ─────────────────────────────────────────────────────────────────────────────
// CORS: reflect requesting Origin; NEVER use '*' when credentials are included.
// Using `origin ?? false` prevents fallback to '*' (breaks credentials).
// ─────────────────────────────────────────────────────────────────────────────
// Replace your corsOptions with this:
const corsOptions: cors.CorsOptions = {
  // Reflect any origin that hits us (needed when credentials = true; cors will echo it back)
  origin: (origin, callback) => callback(null, origin ?? false),

  credentials: true,
  methods: ["GET", "POST", "OPTIONS"],

  // IMPORTANT: let cors reflect Access-Control-Request-Headers automatically.
  // (Remove the hard-coded allowedHeaders array you had before.)
  // allowedHeaders: undefined,  // ← simply omit this key

  maxAge: 86400,
  optionsSuccessStatus: 204, // cleaner preflight result
};




// Helper to add Vary: Origin so caches/CDNs don’t mix origins
function varyOrigin(_req: Request, res: Response, next: NextFunction) {
  res.header("Vary", "Origin");
  next();
}

// ─────────────────────────────────────────────────────────────────────────────
// Webhooks must receive raw body; do NOT run JSON parser or CORS here
// IMPORTANT: Mount BEFORE any json/urlencoded middleware.
// ─────────────────────────────────────────────────────────────────────────────
app.post(
  WEBHOOK_PATH, // "/webhooks"
  express.raw({ type: "application/json", limit: "2mb" }),
  webhooksHandler
);

// ─────────────────────────────────────────────────────────────────────────────
/** JSON parser for everything else (skip /webhooks to preserve raw body) */
// ─────────────────────────────────────────────────────────────────────────────
const jsonParser = express.json({ type: "application/json", limit: "2mb" });
app.use((req: Request, res: Response, next: NextFunction) => {
  if (req.path === WEBHOOK_PATH) return next();
  return jsonParser(req, res, next);
});

// OPTIONAL: urlencoded if you have forms elsewhere (skip /webhooks)
app.use((req: Request, res: Response, next: NextFunction) => {
  if (req.path === WEBHOOK_PATH) return next();
  return express.urlencoded({ extended: true })(req, res, next);
});

// JSON parse error guard (prevents server crash on bad JSON)
app.use((err: any, _req: Request, res: Response, next: NextFunction) => {
  if (err?.type === "entity.parse.failed") {
    logger.error({ err }, "JSON parse failed");
    return res.status(400).json({ error: "Invalid JSON body" });
  }
  return next(err);
});

// ─────────────────────────────────────────────────────────────────────────────
// Mount routes
// ─────────────────────────────────────────────────────────────────────────────
app.use(health);
app.use(install);

// Public API used by storefront — OPEN CORS with credentials (no '*')
app.use("/api", cors(corsOptions), varyOrigin);
app.use(api);

// Explicit preflight for /api/*
app.options("/api/*", cors(corsOptions));

// Global OPTIONS (harmless fallback for other routes)
app.options("*", cors(corsOptions));

// ─────────────────────────────────────────────────────────────────────────────
/** Simple pages */
// ─────────────────────────────────────────────────────────────────────────────
app.use(express.static(path.join(__dirname, "admin-ui/dist/")));
app.get(/^(?!\/api|\/webhooks).*/, (_req, res) => {
  res.sendFile(path.join(__dirname, "admin-ui/dist/index.html"));
});

app.get("/installed", (req, res) => {
  const shop = req.query.shop;
  const host = req.query.host;
  if (shop && host) {
    return res.redirect(`/?shop=${shop}&host=${host}`);
  }
  return res.redirect("/");
});

// ─────────────────────────────────────────────────────────────────────────────
// 404 + Error middleware (keep last)
// ─────────────────────────────────────────────────────────────────────────────
app.use((_req: Request, res: Response) => {
  res.status(404).json({ error: "Not Found" });
});

app.use((err: any, _req: Request, res: Response, _next: NextFunction) => {
  logger.error({ err }, "Unhandled error");
  if (res.headersSent) return;
  res.status(500).json({ error: "Internal Server Error" });
});

// ─────────────────────────────────────────────────────────────────────────────
// Startup, workers & scheduler
// ─────────────────────────────────────────────────────────────────────────────
app.listen(ENV.PORT, () => {
  logger.info(`Server running on http://localhost:${ENV.PORT}`);
  logger.info("Worker not started automatically; run `npm run start:worker` in a separate process.");

  if (String(process.env.ENABLE_SCHEDULER || "false") === "true") {
    const spec = process.env.CRON_SYNC_EVERY || "*/30 * * * *";
    cron.schedule(spec, async () => {
      try {
        const shops = await query<{ shop_domain: string }>(`SELECT shop_domain FROM shop_sessions`);
        for (const shop of shops) {
          await incrementalSync(shop.shop_domain);
        }
      } catch (e) {
        logger.error({ e }, "Cron sync failed");
      }
    });
    logger.info(`Scheduler enabled with cron ${spec}`);
  } else {
    logger.info("Scheduler disabled");
  }
});



