import { eq } from "drizzle-orm";
import type { NextFunction, Response } from "express";
import jwt from "jsonwebtoken";
import type { AuthenticatedRequest, JwtAuthPayload } from "../@types/auth.js";
import { db } from "../db/index.js";
import { customers, staffUsers } from "../db/schema.js";
import { toPublicCustomer, toPublicStaffUser } from "../services/authSerializers.js";
import { verifyAuthToken } from "../services/authSecurity.js";
import { sendError } from "../utils/apiResponse.js";

const getBearerToken = (authorization?: string) => {
  if (!authorization) return null;
  const [scheme, token] = authorization.split(" ");
  if (scheme !== "Bearer" || !token) return null;
  return token;
};

export const optionalAuth = async (req: AuthenticatedRequest, res: Response, next: NextFunction) => {
  const token = getBearerToken(req.headers.authorization);
  if (!token) return next();

  try {
    const payload = verifyAuthToken(token) as JwtAuthPayload;
    const id = Number(payload.sub);

    if (!Number.isInteger(id) || id <= 0) {
      return sendError(res, 401, "INVALID_TOKEN", "Invalid authentication token.");
    }

    if (payload.actorType === "customer") {
      const [customer] = await db.select().from(customers).where(eq(customers.id, id)).limit(1);
      if (!customer) return sendError(res, 401, "INVALID_TOKEN", "Invalid authentication token.");
      req.auth = { actorType: "customer", customer: toPublicCustomer(customer) };
      return next();
    }

    if (payload.actorType === "staff") {
      const [staff] = await db.select().from(staffUsers).where(eq(staffUsers.id, id)).limit(1);
      if (!staff || !staff.isActive) return sendError(res, 401, "INVALID_TOKEN", "Invalid authentication token.");
      req.auth = { actorType: "staff", staff: toPublicStaffUser(staff) };
      return next();
    }

    return sendError(res, 401, "INVALID_TOKEN", "Invalid authentication token.");
  } catch (error) {
    if (error instanceof jwt.TokenExpiredError) {
      return sendError(res, 401, "TOKEN_EXPIRED", "Authentication token expired.");
    }

    return sendError(res, 401, "INVALID_TOKEN", "Invalid authentication token.");
  }
};

export const requireAuth = async (req: AuthenticatedRequest, res: Response, next: NextFunction) => {
  await optionalAuth(req, res, () => {
    if (!req.auth) return sendError(res, 401, "UNAUTHENTICATED", "Authentication required.");
    next();
  });
};

export const requireStaff = async (req: AuthenticatedRequest, res: Response, next: NextFunction) => {
  await requireAuth(req, res, () => {
    if (req.auth?.actorType !== "staff") {
      return sendError(res, 403, "FORBIDDEN", "Staff access required.");
    }
    next();
  });
};

export const requireAdmin = async (req: AuthenticatedRequest, res: Response, next: NextFunction) => {
  await requireStaff(req, res, () => {
    if (req.auth?.actorType !== "staff" || req.auth.staff.role !== "admin") {
      return sendError(res, 403, "FORBIDDEN", "Admin access required.");
    }
    next();
  });
};
