import { Elysia } from "elysia";
import { AuthSession, authSessions } from "../db/schema/authSessions";
import { eq, isNull, and } from "drizzle-orm";
import db from "../db";
import { jwtVerify } from "jose";
import { userService } from "../services/auth/user";
import { protectedRoutes } from "../db/schema";
import { RoleName } from "../services/auth";
import type { User } from "../db/schema/users";

// Константы
const JWT_SECRET = new TextEncoder().encode(process.env.JWT_SECRET);
const BEARER_PREFIX = "Bearer ";
const HEADER_AUTHORIZATION = "Authorization";
const HEADER_GUEST_ID = "X-Guest-Id";

// Типы
interface AuthContext extends Record<string, unknown> {
  authSession: AuthSession | null;
  currentUser: ReturnType<typeof userService.sanitize> | null;
  guestId: string | null;
}

interface JwtPayload {
  id: string;
}

/**
 * Создает контекст без авторизации
 */
const createUnauthenticatedContext = (guestId: string | null): AuthContext => ({
  authSession: null,
  currentUser: null,
  guestId,
});

/**
 * Извлекает токен из заголовка Authorization
 */
const extractAccessToken = (authHeader: string | null): string | null => {
  if (!authHeader?.startsWith(BEARER_PREFIX)) {
    return null;
  }
  
  const token = authHeader.slice(BEARER_PREFIX.length);
  return token || null;
};

/**
 * Верифицирует JWT и возвращает sessionId
 */
const verifyJwtToken = async (token: string): Promise<string | null> => {
  try {
    const { payload } = await jwtVerify<JwtPayload>(token, JWT_SECRET);
    return payload.id || null;
  } catch (error) {
    return null;
  }
};

/**
 * Получает активную сессию из БД
 */
const fetchActiveSession = async (
  sessionId: string
): Promise<AuthSession | null> => {
  try {
    const [session] = await db
      .select()
      .from(authSessions)
      .where(
        and(
          eq(authSessions.id, sessionId),
          isNull(authSessions.revokedAt)
        )
      )
      .limit(1);

    return session || null;
  } catch (error) {
    console.error("Database error while fetching session:", error);
    return null;
  }
};

/**
 * Проверяет, не истекла ли сессия
 */
const isSessionExpired = (session: AuthSession): boolean => {
  return !!(session.expiresAt && session.expiresAt < new Date());
};

/**
 * Верифицирует токен через bcrypt hash
 */
const verifyTokenHash = async (
  token: string,
  hash: string
): Promise<boolean> => {
  try {
    return await Bun.password.verify(token, hash);
  } catch (error) {
    console.error("Token hash verification error:", error);
    return false;
  }
};

/**
 * Проверяет права доступа пользователя к маршруту
 */
const checkRouteAccess = async (
  user: User,
  path: string,
  method: string
): Promise<boolean> => {
  const [route] = await db
    .select({
      methods: protectedRoutes.methods,
      roles: protectedRoutes.roles,
    })
    .from(protectedRoutes)
    .where(eq(protectedRoutes.path, path))
    .limit(1);

  // Если маршрут не защищен, доступ разрешен
  if (!route) {
    return true;
  }

  const { methods, roles } = route;

  // Проверяем, что метод разрешен и роль пользователя подходит
  return methods.includes(method) && roles.includes(user.role as RoleName);
};

/**
 * Optional auth middleware - проверяет авторизацию если токен предоставлен,
 * но не требует его обязательно
 */
export const optionalAuthMiddleware = new Elysia().derive(
  { as: "scoped" },
  async ({ request }): Promise<AuthContext> => {
    const { headers } = request;
    const guestId = headers.get(HEADER_GUEST_ID);
    const authHeader = headers.get(HEADER_AUTHORIZATION);

    // 1. Извлекаем токен
    const accessToken = extractAccessToken(authHeader);
    if (!accessToken) {
      return createUnauthenticatedContext(guestId);
    }

    // 2. Верифицируем JWT
    const sessionId = await verifyJwtToken(accessToken);
    if (!sessionId) {
      return createUnauthenticatedContext(guestId);
    }

    // 3. Получаем активную сессию
    const authSession = await fetchActiveSession(sessionId);
    if (!authSession) {
      return createUnauthenticatedContext(guestId);
    }

    // 4. Проверяем срок действия сессии
    if (isSessionExpired(authSession)) {
      return createUnauthenticatedContext(guestId);
    }

    // 5. Верифицируем hash токена
    const isValidToken = await verifyTokenHash(
      accessToken,
      authSession.accessTokenHash
    );
    if (!isValidToken) {
      return createUnauthenticatedContext(guestId);
    }

    // 6. Получаем пользователя
    const user = await userService.findById(authSession.userId);
    if (!user) {
      return createUnauthenticatedContext(guestId);
    }

    // 7. Проверяем доступ к маршруту
    const url = new URL(request.url);
    const hasAccess = await checkRouteAccess(user, url.pathname, request.method);
    if (!hasAccess) {
      return createUnauthenticatedContext(guestId);
    }

    // 8. Возвращаем успешный контекст
    return {
      authSession,
      currentUser: userService.sanitize(user),
      guestId,
    };
  }
);

