import { NextFunction, Request, Response } from "express";
import { ApiError } from "../errors";
import { env } from "../../config/env";
import { verifyDeviceSecret } from "../store/deviceStore";
import { verifySessionToken, type SessionPayload } from "../services/sessionToken";
import { hasPermission } from "../store/userStore";

export type DeviceAuthContext =
  | {
      mode: "legacy_token";
    }
  | {
      mode: "device_credential";
      device_id: string;
    };

export type RequestWithDeviceAuth = Request & {
  deviceAuth?: DeviceAuthContext;
};

export type AdminAuthContext =
  | {
      mode: "legacy_token";
      user_id: "legacy_admin";
      role_name: "admin";
      permissions: { admin: "*" };
    }
  | {
      mode: "session";
      user_id: string;
      name: string;
      email: string;
      role_id: string;
      role_name: string;
      permissions: unknown;
      session: SessionPayload;
    };

export type RequestWithAdminAuth = Request & {
  adminAuth?: AdminAuthContext;
};

function extractAdminToken(req: Request) {
  const raw = req.header("authorization") || "";
  if (raw.startsWith("Bearer ")) {
    return raw.slice(7);
  }

  return raw || req.header("x-admin-token") || "";
}

export function requireAdminToken(req: RequestWithAdminAuth, _res: Response, next: NextFunction) {
  const token = extractAdminToken(req);
  if (!token) {
    return next(new ApiError("UNAUTHORIZED", "Missing admin bearer token", 401));
  }

  const session = verifySessionToken(token, env.ADMIN_SESSION_SECRET);
  if (session) {
    req.adminAuth = {
      mode: "session",
      user_id: session.sub,
      name: session.name,
      email: session.email,
      role_id: session.role_id,
      role_name: session.role_name,
      permissions: session.permissions,
      session
    };
    return next();
  }

  const allowLegacyToken = env.ADMIN_AUTH_ALLOW_LEGACY_TOKEN !== "false";
  if (!allowLegacyToken || token !== env.ADMIN_TOKEN) {
    return next(new ApiError("UNAUTHORIZED", "Invalid admin token", 401));
  }

  req.adminAuth = {
    mode: "legacy_token",
    user_id: "legacy_admin",
    role_name: "admin",
    permissions: { admin: "*" }
  };
  return next();
}

export function requireAdminPermission(permission: string) {
  return (req: RequestWithAdminAuth, _res: Response, next: NextFunction) => {
    if (!req.adminAuth) {
      return requireAdminToken(req, _res, (error?: unknown) => {
        if (error) {
          return next(error);
        }
        if (!hasPermission(req.adminAuth?.permissions, permission)) {
          return next(new ApiError("FORBIDDEN", "Admin role cannot perform this action", 403));
        }
        return next();
      });
    }

    if (!hasPermission(req.adminAuth.permissions, permission)) {
      return next(new ApiError("FORBIDDEN", "Admin role cannot perform this action", 403));
    }
    return next();
  };
}

export function requireDeviceToken(req: RequestWithDeviceAuth, _res: Response, next: NextFunction) {
  const raw = req.header("authorization") || "";
  const token = raw.startsWith("Bearer ") ? raw.slice(7) : raw;
  const headerDeviceId = req.header("x-device-id") || "";
  const headerSecret = req.header("x-device-secret") || "";
  const allowLegacyToken = env.DEVICE_AUTH_ALLOW_LEGACY_TOKEN !== "false";

  if (headerDeviceId && headerSecret) {
    verifyDeviceSecret(headerDeviceId, headerSecret)
      .then((device) => {
        if (!device) {
          return next(new ApiError("UNAUTHORIZED", "Invalid device credential", 401));
        }
        req.deviceAuth = {
          mode: "device_credential",
          device_id: device.id
        };
        return next();
      })
      .catch((error) => next(error));
    return;
  }

  if (!allowLegacyToken) {
    return next(new ApiError("UNAUTHORIZED", "Device bearer token fallback is disabled", 401));
  }

  if (!token) {
    return next(new ApiError("UNAUTHORIZED", "Missing device credential", 401));
  }

  if (token !== env.DEVICE_TOKEN) {
    return next(new ApiError("UNAUTHORIZED", "Invalid device token", 401));
  }

  req.deviceAuth = {
    mode: "legacy_token"
  };
  return next();
}

export function getDeviceScopeError(req: RequestWithDeviceAuth, deviceId: string): ApiError | null {
  if (req.deviceAuth?.mode === "device_credential" && req.deviceAuth.device_id !== deviceId) {
    return new ApiError("FORBIDDEN", "device credential cannot access another device", 403);
  }
  return null;
}
