import { Injectable, UnauthorizedException } from '@nestjs/common';
import { PassportStrategy } from '@nestjs/passport';
import { ExtractJwt, Strategy } from 'passport-jwt';
import type { Request } from 'express';

import { PrismaService } from '@/prisma/prisma.service';
import { SESSION_IDLE_TIMEOUT_MS } from './auth.service';

export interface JwtPayload {
  userId: number;
  sessionId: string;
}

const AUTH_TOKEN_COOKIE_NAME = 'accessToken';
const SESSION_ACTIVITY_UPDATE_INTERVAL_MS = 60 * 1000;

type RequestWithUnknownCookies = {
  cookies?: unknown;
};

function extractJwtFromCookie(request: Request): string | null {
  const cookies = (request as unknown as RequestWithUnknownCookies).cookies;

  if (!cookies || typeof cookies !== 'object') {
    return null;
  }

  const token = (cookies as Record<string, unknown>)[AUTH_TOKEN_COOKIE_NAME];

  return typeof token === 'string' ? token : null;
}

@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
  constructor(private readonly prisma: PrismaService) {
    const secret = process.env.JWT_SECRET;
    if (!secret) throw new Error('JWT_SECRET is not set');

    super({
      jwtFromRequest: ExtractJwt.fromExtractors([
        ExtractJwt.fromAuthHeaderAsBearerToken(),
        extractJwtFromCookie,
      ]),
      ignoreExpiration: true,
      secretOrKey: secret,
    });
  }

  async validate(payload: JwtPayload) {
    if (typeof payload.userId !== 'number' || !payload.sessionId) {
      throw new UnauthorizedException('Invalid token payload');
    }

    const session = await this.prisma.userSession.findUnique({
      where: { id: payload.sessionId },
    });
    if (!session || session.userId !== payload.userId) {
      throw new UnauthorizedException();
    }

    const now = new Date();

    if (session.loggedOutAt) {
      throw new UnauthorizedException('Session has been logged out');
    }

    if (session.expiresAt <= now) {
      throw new UnauthorizedException('Session expired due to inactivity');
    }

    const user = await this.prisma.user.findFirst({
      where: { id: payload.userId, deletedAt: null, isActive: true },
      select: {
        id: true,
        email: true,
        name: true,
        createdAt: true,
      },
    });
    if (!user) throw new UnauthorizedException();

    const timeSinceLastActivity =
      now.getTime() - session.lastActivityAt.getTime();

    if (timeSinceLastActivity >= SESSION_ACTIVITY_UPDATE_INTERVAL_MS) {
      await this.prisma.userSession.update({
        where: { id: session.id },
        data: {
          lastActivityAt: now,
          expiresAt: new Date(now.getTime() + SESSION_IDLE_TIMEOUT_MS),
        },
      });
    }

    return { ...user, sessionId: session.id };
  }
}
