import createError from "http-errors";
import express, { NextFunction, Request, Response } from "express";
import path from "path";
import cookieParser from "cookie-parser";
import logger from "morgan";
import helmet from "helmet";
import compression from "compression";
import passport from "passport";
import { csrfSync } from "csrf-sync";
import rateLimit from "express-rate-limit";
import cors from "cors";
import flash from "connect-flash";
import hpp from "hpp";

import passportConfig from "./passport";
import apiRouter from "./routes/apis";
import authRouter from "./routes/auth";
import emailTemplatesRouter from "./routes/email-templates";
import public_router from "./routes/public";
import { isInternalServer, isProduction } from "./shared/utils";
import sessionMiddleware from "./middlewares/session-middleware";
import safeControllerFunction from "./shared/safe-controller-function";
import AwsSesController from "./controllers/aws-ses-controller";
import { CSP_POLICIES } from "./shared/csp";

const app = express();

// Trust first proxy if behind reverse proxy
app.set("trust proxy", 1);

// Basic middleware setup
app.use(compression());
app.use(logger("dev"));
app.use(express.json({ limit: "50mb" }));
app.use(express.urlencoded({ extended: false, limit: "50mb" }));
app.use(cookieParser(process.env.COOKIE_SECRET));
app.use(hpp());

// Helmet security headers
app.use(helmet({
  crossOriginEmbedderPolicy: false,
  crossOriginResourcePolicy: false,
}));

// Custom security headers
app.use((_req: Request, res: Response, next: NextFunction) => {
  res.setHeader("X-XSS-Protection", "1; mode=block");
  res.removeHeader("server");
  res.setHeader("Content-Security-Policy", CSP_POLICIES);
  next();
});

// CORS configuration
const allowedOrigins = [
  isProduction() 
    ? [
        `http://localhost:5000`,
        `http://127.0.0.1:5000`,
        process.env.SERVER_CORS || "",  // Add hostname from env
        process.env.FRONTEND_URL || ""  // Support FRONTEND_URL as well
      ].filter(Boolean)  // Remove empty strings
    : [
        "http://localhost:3000",
        "http://localhost:5173",
        "http://127.0.0.1:5173",
        "http://127.0.0.1:3000",
        "http://127.0.0.1:5000",
        `http://localhost:5000`,
        process.env.SERVER_CORS || "",  // Add hostname from env
        process.env.FRONTEND_URL || ""  // Support FRONTEND_URL as well
      ].filter(Boolean)  // Remove empty strings
].flat();

app.use(cors({
  origin: (origin, callback) => {
    if (!isProduction() || !origin || allowedOrigins.includes(origin)) {
      callback(null, true);
    } else {
      console.log("Blocked origin:", origin, process.env.NODE_ENV);
      callback(new Error("Not allowed by CORS"));
    }
  },
  credentials: true,
  methods: ["GET", "POST", "PUT", "DELETE", "OPTIONS", "PATCH"],
  allowedHeaders: [
    "Origin",
    "X-Requested-With",
    "Content-Type",
    "Accept",
    "Authorization",
    "X-CSRF-Token"
  ],
  exposedHeaders: ["Set-Cookie", "X-CSRF-Token"]
}));

// Handle preflight requests
app.options("*", cors());

// Session setup - must be before passport and CSRF
app.use(sessionMiddleware);

// Passport initialization
passportConfig(passport);
app.use(passport.initialize());
app.use(passport.session());

// Flash messages
app.use(flash());

// Auth check middleware
function isLoggedIn(req: Request, _res: Response, next: NextFunction) {
  return req.user ? next() : next(createError(401));
}

// CSRF configuration using csrf-sync for session-based authentication
const {
  invalidCsrfTokenError,
  generateToken,
  csrfSynchronisedProtection,
} = csrfSync({
  getTokenFromRequest: (req: Request) => req.headers["x-csrf-token"] as string || (req.body && req.body["_csrf"])
});

// Apply CSRF selectively (exclude webhooks and public routes)
app.use((req, res, next) => {
  if (
    req.path.startsWith("/webhook/") ||
    req.path.startsWith("/secure/") ||
    req.path.startsWith("/api/") ||
    req.path.startsWith("/public/")
  ) {
    next();
  } else {
    csrfSynchronisedProtection(req, res, next);
  }
});

// Set CSRF token method on request object for compatibility
app.use((req: Request, res: Response, next: NextFunction) => {
  // Add csrfToken method to request object for compatibility
  if (!req.csrfToken && generateToken) {
    req.csrfToken = (overwrite?: boolean) => generateToken(req, overwrite);
  }
  next();
});

// CSRF token refresh endpoint
app.get("/csrf-token", (req: Request, res: Response) => {
  try {
    const token = generateToken(req);
    res.status(200).json({ done: true, message: "CSRF token refreshed", token });
  } catch (error) {
    res.status(500).json({ done: false, message: "Failed to generate CSRF token" });
  }
});

// Webhook endpoints (no CSRF required)
app.post("/webhook/emails/bounce", safeControllerFunction(AwsSesController.handleBounceResponse));
app.post("/webhook/emails/complaints", safeControllerFunction(AwsSesController.handleComplaintResponse));
app.post("/webhook/emails/reply", safeControllerFunction(AwsSesController.handleReplies));

// Static file serving
if (isProduction()) {
  app.use(express.static(path.join(__dirname, "build"), {
    maxAge: "1y",
    etag: false,
  }));

  // Handle compressed files
  app.get("*.js", (req, res, next) => {
    if (req.header("Accept-Encoding")?.includes("br")) {
      req.url = `${req.url}.br`;
      res.set("Content-Encoding", "br");
      res.set("Content-Type", "application/javascript; charset=UTF-8");
    } else if (req.header("Accept-Encoding")?.includes("gzip")) {
      req.url = `${req.url}.gz`;
      res.set("Content-Encoding", "gzip");
      res.set("Content-Type", "application/javascript; charset=UTF-8");
    }
    next();
  });
} else {
  app.use(express.static(path.join(__dirname, "public")));
}

// API rate limiting
const apiLimiter = rateLimit({
  windowMs: 15 * 60 * 1000,
  max: 1500,
  standardHeaders: false,
  legacyHeaders: false,
});

// Routes
app.use("/api/v1", apiLimiter, isLoggedIn, apiRouter);
app.use("/secure", authRouter);
app.use("/public", public_router);

if (isInternalServer()) {
  app.use("/email-templates", emailTemplatesRouter);
}

// CSRF error handler
app.use((err: any, req: Request, res: Response, next: NextFunction) => {
  if (err === invalidCsrfTokenError) {
    return res.status(403).json({
      done: false,
      message: "Invalid CSRF token",
      body: null
    });
  }
  next(err);
});

// React app handling - serve index.html for all non-API routes
app.get("*", (req: Request, res: Response, next: NextFunction) => {
  if (req.path.startsWith("/api/")) return next();
  res.sendFile(path.join(__dirname, isProduction() ? "build" : "public", "index.html"));
});

// Global error handler
app.use((err: any, _req: Request, res: Response, _next: NextFunction) => {
  const status = err.status || 500;

  if (res.headersSent) {
    return;
  }

  res.status(status);

  // Send structured error response
  res.json({
    done: false,
    message: isProduction() ? "Internal Server Error" : err.message,
    body: null,
    ...(process.env.NODE_ENV === "development" ? { stack: err.stack } : {})
  });
});

export default app;