import { Router, Request, Response, NextFunction } from "express";
import bcrypt from "bcryptjs";
import jwt from "jsonwebtoken";
import { z } from "zod";
import { prisma } from "../lib/prisma.js";
import { AppError } from "../middleware/errorHandler.js";
import { requireAuth } from "../middleware/auth.js";
import { AuthenticatedRequest } from "../types/index.js";

const router = Router();

const ACCESS_TOKEN_TTL = "15m";
const REFRESH_TOKEN_TTL = "7d";
const REFRESH_TOKEN_TTL_MS = 7 * 24 * 60 * 60 * 1000;

function signAccessToken(payload: object): string {
  const secret = process.env.JWT_SECRET;
  if (!secret) throw new Error("JWT_SECRET not configured");
  return jwt.sign(payload, secret, { expiresIn: ACCESS_TOKEN_TTL });
}

function signRefreshToken(payload: object): string {
  const secret = process.env.JWT_SECRET;
  if (!secret) throw new Error("JWT_SECRET not configured");
  return jwt.sign(payload, secret, { expiresIn: REFRESH_TOKEN_TTL });
}

// POST /api/v1/auth/login
const LoginSchema = z.object({
  email: z.string().email(),
  password: z.string().min(1),
});

router.post(
  "/login",
  async (req: Request, res: Response, next: NextFunction) => {
    try {
      const body = LoginSchema.parse(req.body);

      const user = await prisma.user.findUnique({
        where: { email: body.email },
        include: { role: true, vendor: true },
      });

      if (!user || !(await bcrypt.compare(body.password, user.passwordHash))) {
        throw new AppError(401, "INVALID_CREDENTIALS", "Invalid email or password");
      }

      const tokenPayload = {
        userId: user.id,
        vendorId: user.vendorId,
        roleId: user.roleId,
        roleName: user.role.name,
      };

      const accessToken = signAccessToken(tokenPayload);
      const refreshToken = signRefreshToken({ userId: user.id });

      await prisma.refreshToken.create({
        data: {
          token: refreshToken,
          userId: user.id,
          expiresAt: new Date(Date.now() + REFRESH_TOKEN_TTL_MS),
        },
      });

      res.json({
        accessToken,
        refreshToken,
        user: {
          id: user.id,
          email: user.email,
          name: user.name,
          role: user.role.name,
          vendorId: user.vendorId,
          vendorName: user.vendor.name,
        },
      });
    } catch (err) {
      next(err);
    }
  }
);

// POST /api/v1/auth/refresh
const RefreshSchema = z.object({
  refreshToken: z.string().min(1),
});

router.post(
  "/refresh",
  async (req: Request, res: Response, next: NextFunction) => {
    try {
      const { refreshToken } = RefreshSchema.parse(req.body);
      const secret = process.env.JWT_SECRET;
      if (!secret) throw new AppError(500, "CONFIG_ERROR", "JWT secret not configured");

      let decoded: { userId: string };
      try {
        decoded = jwt.verify(refreshToken, secret) as { userId: string };
      } catch {
        throw new AppError(401, "INVALID_TOKEN", "Invalid or expired refresh token");
      }

      const stored = await prisma.refreshToken.findUnique({
        where: { token: refreshToken },
        include: { user: { include: { role: true } } },
      });

      if (!stored || stored.userId !== decoded.userId || stored.expiresAt < new Date()) {
        throw new AppError(401, "INVALID_TOKEN", "Refresh token not found or expired");
      }

      // Rotate refresh token
      await prisma.refreshToken.delete({ where: { id: stored.id } });

      const user = stored.user;
      const tokenPayload = {
        userId: user.id,
        vendorId: user.vendorId,
        roleId: user.roleId,
        roleName: user.role.name,
      };

      const newAccessToken = signAccessToken(tokenPayload);
      const newRefreshToken = signRefreshToken({ userId: user.id });

      await prisma.refreshToken.create({
        data: {
          token: newRefreshToken,
          userId: user.id,
          expiresAt: new Date(Date.now() + REFRESH_TOKEN_TTL_MS),
        },
      });

      res.json({ accessToken: newAccessToken, refreshToken: newRefreshToken });
    } catch (err) {
      next(err);
    }
  }
);

// POST /api/v1/auth/logout
router.post(
  "/logout",
  requireAuth as unknown as (req: Request, res: Response, next: NextFunction) => void,
  async (req: Request, res: Response, next: NextFunction) => {
    try {
      const authReq = req as AuthenticatedRequest;
      const { refreshToken } = z.object({ refreshToken: z.string().optional() }).parse(req.body);

      if (refreshToken) {
        await prisma.refreshToken.deleteMany({
          where: { token: refreshToken, userId: authReq.auth.userId },
        });
      } else {
        // Logout all devices
        await prisma.refreshToken.deleteMany({
          where: { userId: authReq.auth.userId },
        });
      }

      res.json({ message: "Logged out successfully" });
    } catch (err) {
      next(err);
    }
  }
);

// GET /api/v1/auth/me
router.get(
  "/me",
  requireAuth as unknown as (req: Request, res: Response, next: NextFunction) => void,
  async (req: Request, res: Response, next: NextFunction) => {
    try {
      const authReq = req as AuthenticatedRequest;
      const user = await prisma.user.findUnique({
        where: { id: authReq.auth.userId },
        include: { role: true, vendor: true },
      });

      if (!user) throw new AppError(404, "NOT_FOUND", "User not found");

      res.json({
        id: user.id,
        email: user.email,
        name: user.name,
        role: user.role.name,
        vendorId: user.vendorId,
        vendorName: user.vendor.name,
      });
    } catch (err) {
      next(err);
    }
  }
);

export default router;
