import { Request, Response, NextFunction } from "express";
import jwt from "jsonwebtoken";
import { config } from "../config";
import { PrismaClient } from "../generated/client";
import { prisma as defaultPrisma } from "../db/prisma";
import { createAuthModeService, type AuthModeService } from "../auth/authMode";
import { ACCESS_TOKEN_COOKIE_NAME, readCookie } from "../auth/cookies";

// Extend Express Request type to include user
declare global {
  namespace Express {
    interface Request {
      user?: {
        id: string;
        username?: string | null;
        email: string;
        name: string;
        role: string;
        mustResetPassword?: boolean;
        impersonatorId?: string;
      };
    }
  }
}

interface JwtPayload {
  userId: string;
  email: string;
  type: "access" | "refresh";
  impersonatorId?: string;
}

const isJwtPayload = (decoded: unknown): decoded is JwtPayload => {
  if (typeof decoded !== "object" || decoded === null) {
    return false;
  }
  const payload = decoded as Record<string, unknown>;
  const impersonatorOk =
    typeof payload.impersonatorId === "undefined" || typeof payload.impersonatorId === "string";
  return (
    typeof payload.userId === "string" &&
    typeof payload.email === "string" &&
    (payload.type === "access" || payload.type === "refresh") &&
    impersonatorOk
  );
};

const extractToken = (req: Request): string | null => {
  const authHeader = req.headers.authorization;
  if (authHeader && typeof authHeader === "string") {
    const parts = authHeader.split(" ");
    if (parts.length === 2 && parts[0] === "Bearer") {
      return parts[1] || null;
    }
  }

  return readCookie(req, ACCESS_TOKEN_COOKIE_NAME);
};

const verifyToken = (token: string): JwtPayload | null => {
  try {
    const decoded = jwt.verify(token, config.jwtSecret);
    if (!isJwtPayload(decoded)) {
      return null;
    }
    if (decoded.type !== "access") {
      return null; // Only accept access tokens in middleware
    }
    return decoded;
  } catch {
    return null;
  }
};

const normalizeRequestPath = (req: Request): string => {
  const raw = (req.originalUrl || req.url || "").split("?")[0] || "";
  // In some deployments the backend may see a /api prefix.
  return raw.replace(/^\/api(?=\/)/, "");
};

const isAllowedWhileMustResetPassword = (req: Request): boolean => {
  const path = normalizeRequestPath(req);

  // Permit fetching current user and changing password.
  if (req.method === "GET" && path === "/auth/me") return true;
  if (req.method === "POST" && path === "/auth/change-password") return true;
  if (req.method === "POST" && path === "/auth/must-reset-password") return true;

  return false;
};

export type AuthMiddlewareDeps = {
  prisma: PrismaClient;
  authModeService: AuthModeService;
};

export const createAuthMiddleware = ({
  prisma,
  authModeService,
}: AuthMiddlewareDeps) => {
  const requireAuth = async (
    req: Request,
    res: Response,
    next: NextFunction
  ): Promise<void> => {
    // Single-user mode: authentication disabled -> treat all requests as the bootstrap user.
    try {
      const authEnabled = await authModeService.getAuthEnabled();
      if (!authEnabled) {
        const user = await authModeService.getBootstrapActingUser();
        req.user = {
          id: user.id,
          username: user.username,
          email: user.email,
          name: user.name,
          role: user.role,
          mustResetPassword: user.mustResetPassword,
        };
        return next();
      }
    } catch (error) {
      console.error("Error reading auth mode:", error);
      res.status(500).json({
        error: "Internal server error",
        message: "Failed to read authentication mode",
      });
      return;
    }

    const token = extractToken(req);

    if (!token) {
      res.status(401).json({
        error: "Unauthorized",
        message: "Authentication token required",
      });
      return;
    }

    const payload = verifyToken(token);

    if (!payload) {
      res.status(401).json({
        error: "Unauthorized",
        message: "Invalid or expired token",
      });
      return;
    }

    // Verify user still exists and is active
    try {
      const user = await prisma.user.findUnique({
        where: { id: payload.userId },
        select: {
          id: true,
          username: true,
          email: true,
          name: true,
          role: true,
          mustResetPassword: true,
          isActive: true,
        },
      });

      if (!user || !user.isActive) {
        res.status(401).json({
          error: "Unauthorized",
          message: "User account not found or inactive",
        });
        return;
      }

      if (user.mustResetPassword && !isAllowedWhileMustResetPassword(req)) {
        res.status(403).json({
          error: "Forbidden",
          code: "MUST_RESET_PASSWORD",
          message: "You must reset your password before using the app",
        });
        return;
      }

      // Attach user to request
      req.user = {
        id: user.id,
        username: user.username,
        email: user.email,
        name: user.name,
        role: user.role,
        mustResetPassword: user.mustResetPassword,
        impersonatorId: payload.impersonatorId,
      };

      next();
    } catch (error) {
      console.error("Error verifying user:", error);
      res.status(500).json({
        error: "Internal server error",
        message: "Failed to verify user",
      });
    }
  };

  const optionalAuth = async (
    req: Request,
    res: Response,
    next: NextFunction
  ): Promise<void> => {
    try {
      const authEnabled = await authModeService.getAuthEnabled();
      if (!authEnabled) {
        return next();
      }
    } catch (error) {
      console.error("Error reading auth mode:", error);
      return next();
    }

    const token = extractToken(req);

    if (!token) {
      return next();
    }

    const payload = verifyToken(token);

    if (!payload) {
      return next();
    }

    try {
      const user = await prisma.user.findUnique({
        where: { id: payload.userId },
        select: {
          id: true,
          username: true,
          email: true,
          name: true,
          role: true,
          mustResetPassword: true,
          isActive: true,
        },
      });

      if (user && user.isActive) {
        req.user = {
          id: user.id,
          username: user.username,
          email: user.email,
          name: user.name,
          role: user.role,
          mustResetPassword: user.mustResetPassword,
          impersonatorId: payload.impersonatorId,
        };
      }
    } catch (error) {
      // Silently fail for optional auth
      console.error("Error in optional auth:", error);
    }

    next();
  };

  return {
    requireAuth,
    optionalAuth,
  };
};

const defaultAuthModeService = createAuthModeService(defaultPrisma);
const defaultAuthMiddleware = createAuthMiddleware({
  prisma: defaultPrisma,
  authModeService: defaultAuthModeService,
});

export const authModeService = defaultAuthModeService;
export const requireAuth = defaultAuthMiddleware.requireAuth;
export const optionalAuth = defaultAuthMiddleware.optionalAuth;
