import { FastifyRequest, FastifyReply } from 'fastify';
import jwt from 'jsonwebtoken';
import { UserRole, UserStatus } from '@prisma/client';
import { prisma } from '../../../config/database';
import { env } from '../../../config/env';
import { hasAnyRole, MEDIA_ROLES, getUserRoles } from '../../../utils/roles';

// Extend FastifyRequest to include user
declare module 'fastify' {
  interface FastifyRequest {
    user?: {
      id: string;
      email: string;
      role: UserRole;
      roles: UserRole[];
    };
  }
}

interface TokenPayload {
  id: string;
  email: string;
  role: UserRole;
  roles?: UserRole[];
}

/**
 * Authenticate user via V2 JWT access token
 * Verifies token and checks user status in Prisma database
 */
export async function authenticate(
  request: FastifyRequest,
  reply: FastifyReply
): Promise<void> {
  const authHeader = request.headers.authorization;
  const queryToken = (request.query as Record<string, string>)?.token;

  // Support both Authorization header and ?token= query param (for <img>/<video> src)
  let token: string | null = null;
  if (authHeader?.startsWith('Bearer ')) {
    token = authHeader.substring(7);
  } else if (queryToken) {
    token = queryToken;
  }

  if (!token) {
    return reply.status(401).send({
      error: 'Authentication required',
      code: 'AUTH_REQUIRED'
    });
  }

  // Verify JWT with V2 access secret
  let payload: TokenPayload;
  try {
    payload = jwt.verify(token, env.JWT_ACCESS_SECRET, { algorithms: ['HS256'] }) as TokenPayload;
  } catch (error) {
    return reply.status(401).send({
      error: 'Invalid or expired token',
      code: 'INVALID_TOKEN'
    });
  }

  // Verify user still exists and is active
  const user = await prisma.user.findUnique({
    where: { id: payload.id },
    select: {
      id: true,
      email: true,
      role: true,
      roles: true,
      status: true,
      expiresAt: true,
    },
  });

  if (!user) {
    return reply.status(401).send({
      error: 'User not found',
      code: 'USER_NOT_FOUND'
    });
  }

  if (user.status !== UserStatus.ACTIVE) {
    return reply.status(403).send({
      error: `Account is ${user.status.toLowerCase()}`,
      code: 'ACCOUNT_INACTIVE'
    });
  }

  // Check expiration if set
  if (user.expiresAt && user.expiresAt < new Date()) {
    return reply.status(403).send({
      error: 'Account has expired',
      code: 'ACCOUNT_EXPIRED'
    });
  }

  // Attach user to request
  const userRoles = getUserRoles(user);
  request.user = {
    id: user.id,
    email: user.email,
    role: user.role as UserRole,
    roles: userRoles,
  };
}

/**
 * Require admin role (SUPER_ADMIN, INFLUENCE_ADMIN, or MAP_ADMIN)
 * Also checks auth, so no need to use authenticate() separately
 */
export async function requireAdminRole(
  request: FastifyRequest,
  reply: FastifyReply
): Promise<void> {
  // First authenticate
  await authenticate(request, reply);

  // If authenticate sent a response, stop here
  if (reply.sent) {
    return;
  }

  // Check admin role using multi-role utility
  if (!request.user || !hasAnyRole(request.user, MEDIA_ROLES)) {
    return reply.status(403).send({
      error: 'Admin access required',
      code: 'ADMIN_REQUIRED'
    });
  }
}

/**
 * Optional authentication - attach user if token present, but don't require it
 * Used for public endpoints that want to know if user is logged in
 */
export async function optionalAuth(
  request: FastifyRequest,
  _reply: FastifyReply
): Promise<void> {
  const authHeader = request.headers.authorization;
  const queryToken = (request.query as Record<string, string>)?.token;

  let token: string | null = null;
  if (authHeader?.startsWith('Bearer ')) {
    token = authHeader.substring(7);
  } else if (queryToken) {
    token = queryToken;
  }

  if (!token) {
    return;
  }

  try {
    const payload = jwt.verify(token, env.JWT_ACCESS_SECRET, { algorithms: ['HS256'] }) as TokenPayload;

    // Verify user exists and is active
    const user = await prisma.user.findUnique({
      where: { id: payload.id },
      select: {
        id: true,
        email: true,
        role: true,
        roles: true,
        status: true,
      },
    });

    if (user && user.status === UserStatus.ACTIVE) {
      const userRoles = getUserRoles(user);
      request.user = {
        id: user.id,
        email: user.email,
        role: user.role as UserRole,
        roles: userRoles,
      };
    }
  } catch {
    // Invalid token, just ignore and continue without user
  }
}
