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";

// JWT секрет (должен совпадать с session.service.ts)
const JWT_SECRET = new TextEncoder().encode(process.env.JWT_SECRET);

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

    // Если нет заголовка авторизации, продолжаем без пользователя
    if (!authHeader || !authHeader.startsWith("Bearer ")) {
      return {
        authSession: null,
        currentUser: null,
      };
    }

    const accessToken = authHeader.split(" ")[1];

    if (!accessToken) {
      return {
        authSession: null,
        currentUser: null,
      };
    }

    // 2. Верифицировать JWT (проверка подписи и срока действия)
    let sessionId: string;

    try {
      const { payload } = await jwtVerify<{ id: string }>(
        accessToken,
        JWT_SECRET
      );

      sessionId = payload.id;

      if (!sessionId) {
        return {
          authSession: null,
          currentUser: null,
        };
      }
    } catch (err) {
      return {
        authSession: null,
        currentUser: null,
      };
    }

    // 3. Получить сессию из БД и проверить её валидность
    let authSession: AuthSession;

    try {
      authSession = (
        await db
          .select()
          .from(authSessions)
          .where(
            and(
              eq(authSessions.id, sessionId),
              isNull(authSessions.revokedAt) // Сессия не отозвана
            )
          )
          .limit(1)
      )[0];

      if (!authSession) {
        return {
          authSession: null,
          currentUser: null,
        };
      }
    } catch (err) {
      console.error("Database error in optional auth middleware:", err);
      return {
        authSession: null,
        currentUser: null,
      };
    }

    // 4. Проверить срок действия сессии
    if (authSession.expiresAt && authSession.expiresAt < new Date()) {
      return {
        authSession: null,
        currentUser: null,
      };
    }

    // 5. Верифицировать bcrypt hash токена
    try {
      const verified = await Bun.password.verify(
        accessToken,
        authSession.accessTokenHash
      );

      if (!verified) {
        return {
          authSession: null,
          currentUser: null,
        };
      }
    } catch (err) {
      console.error("Token verification error:", err);
      return {
        authSession: null,
        currentUser: null,
      };
    }

    // 6. Получить пользователя
    const user = await userService.findById(authSession.userId);

    if (!user) {
      return {
        authSession: null,
        currentUser: null,
      };
    }

    // 7. Проверить доступ к маршруту на основе ролей (если маршрут защищен)
    const url = new URL(request.url);
    const path = url.pathname;
    const method = request.method;

    // Получить маршрут из БД
    const route = await db
      .select({
        methods: protectedRoutes.methods,
        roles: protectedRoutes.roles,
      })
      .from(protectedRoutes)
      .where(eq(protectedRoutes.path, path))
      .limit(1);

    // Если маршрут защищен, проверить права доступа
    if (route.length > 0) {
      const allowedMethods = route[0].methods;
      const allowedRoles = route[0].roles;

      // Проверить, что метод входит в список разрешенных
      if (allowedMethods.includes(method)) {
        // Проверить, есть ли роль пользователя среди разрешенных
        if (!allowedRoles.includes(user.role as RoleName)) {
          return {
            authSession: null,
            currentUser: null,
          };
        }
      }
    }

    // 8. Всё ОК - вернуть сессию и санитизированного пользователя (без пароля)
    return {
      authSession,
      currentUser: userService.sanitize(user),
    };
  }
);

