import * as fs from "fs";

import "dotenv/config";
import cors from "cors";
import express from "express";
import morgan from "morgan";

import { requireUpstreamSecret } from "./auth";
import { isAllowedSubmitter } from "./allowlist";
import { getJob, getOrCreateJob, RequestConflictError } from "./jobStore";
import { runPublishJob } from "./publishEvent";
import { UPLOAD_DIR, uploadRouter } from "./uploadRoute";
import { ValidationError, validatePublishRequest } from "./validateRequest";

const PORT = Number(process.env.PORT) || 8001;
const MAX_BODY_SIZE = "1mb";

fs.mkdirSync(UPLOAD_DIR, { recursive: true });

const app = express();
app.use(morgan("combined"));
app.use(cors());
app.use(express.json({ limit: MAX_BODY_SIZE }));
app.use("/uploads", express.static(UPLOAD_DIR));
app.use(uploadRouter);

app.get("/health", (_req, res) => {
  res.json({ status: "ok" });
});

app.post("/api/events/publish", requireUpstreamSecret, async (req, res) => {
  let publishRequest;
  try {
    publishRequest = validatePublishRequest(req.body);
  } catch (error) {
    if (error instanceof ValidationError) {
      res.status(400).json({ error: { code: "VALIDATION_ERROR", message: error.message } });
      return;
    }
    throw error;
  }

  if (!isAllowedSubmitter(publishRequest.submittedByEmail)) {
    res.status(403).json({
      error: {
        code: "SUBMITTER_NOT_ALLOWED",
        message:
          "This WatIAM ID is not on the allowlist for submitting events. " +
          "Ask the web committee on Discord to be added.",
      },
    });
    return;
  }

  let job;
  try {
    const result = getOrCreateJob(publishRequest);
    job = result.job;

    if (result.isNew) {
      // Awaited so the response reflects the final outcome when it fits
      // inside the caller's timeout; jobStore keeps progressing even if
      // the connection is dropped mid-request (see publishEvent.ts).
      await runPublishJob(job);
    }
  } catch (error) {
    if (error instanceof RequestConflictError) {
      res.status(409).json({ error: { code: "REQUEST_ID_CONFLICT", message: error.message } });
      return;
    }
    throw error;
  }

  const latest = getJob(publishRequest.requestId)!;
  res.status(latest.status === "failed" ? 500 : 201).json({
    requestId: latest.requestId,
    status: latest.status,
    branchName: latest.branchName ?? null,
    pullRequestUrl: latest.pullRequestUrl ?? null,
    discord: latest.discord ?? null,
    error: latest.error ?? null,
  });
});

app.get("/api/events/:requestId", requireUpstreamSecret, (req, res) => {
  const job = getJob(req.params.requestId);
  if (!job) {
    res.status(404).json({ error: { code: "NOT_FOUND", message: "Unknown requestId" } });
    return;
  }

  res.json({
    requestId: job.requestId,
    status: job.status,
    branchName: job.branchName ?? null,
    pullRequestUrl: job.pullRequestUrl ?? null,
    discord: job.discord ?? null,
    error: job.error ?? null,
    createdAt: job.createdAt,
    updatedAt: job.updatedAt,
  });
});

// express.json() body-parser error handler (e.g. payload too large, bad JSON)
app.use((error: any, _req: express.Request, res: express.Response, _next: express.NextFunction) => {
  if (error?.type === "entity.too.large") {
    res.status(413).json({ error: { code: "PAYLOAD_TOO_LARGE", message: `Body must not exceed ${MAX_BODY_SIZE}` } });
    return;
  }
  if (error?.type === "entity.parse.failed") {
    res.status(400).json({ error: { code: "INVALID_JSON", message: "Request body must be valid JSON" } });
    return;
  }
  console.error(error);
  res.status(500).json({ error: { code: "INTERNAL_ERROR", message: "Unexpected error" } });
});

// In production, listen on a Unix socket (same convention as the legacy
// Eventer app) so the same reverse-proxy setup can front this service.
const APP_SOCKET_PATH = "./app.sock";

if (process.env.NODE_ENV === "production") {
  if (fs.existsSync(APP_SOCKET_PATH)) {
    fs.rmSync(APP_SOCKET_PATH);
  }
  app.listen(APP_SOCKET_PATH, () => {
    fs.chmodSync(APP_SOCKET_PATH, 0o777);
    console.log(`EventCentral backend (v1 prototype) listening on ${APP_SOCKET_PATH}`);
  });
} else {
  app.listen(PORT, () => {
    console.log(`EventCentral backend (v1 prototype) listening on http://localhost:${PORT}`);
  });
}
