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';
import { verifyMediaSignature } from '../../../utils/signed-url';

// 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 query = (request.query as Record<string, string>) ?? {};

  // Two accepted auth paths:
  //   1. `Authorization: Bearer <JWT>` — normal API use (mobile, fetch, etc.)
  //   2. Signed-URL query params `?sig=...&exp=...&uid=...` — used for
  //      `<img src>`/`<video src>` tags where browsers can't set headers.
  //      This replaces the legacy `?token=<JWT>` path on 2026-04-12:
  //      full JWTs in URLs were leaking via logs, referer headers, and
  //      shared-link copy/paste. Signed URLs are path-scoped and 5-min TTL.

  let authenticatedUserId: string | null = null;

  if (authHeader?.startsWith('Bearer ')) {
    const token = authHeader.substring(7);
    try {
      const payload = jwt.verify(token, env.JWT_ACCESS_SECRET, { algorithms: ['HS256'] }) as TokenPayload;
      authenticatedUserId = payload.id;
    } catch {
      return reply.status(401).send({ error: 'Invalid or expired token', code: 'INVALID_TOKEN' });
    }
  } else if (query.sig && query.exp && query.uid) {
    const result = verifyMediaSignature(request.url, query);
    if (!result.valid) {
      return reply.status(401).send({ error: 'Invalid signed URL', code: 'INVALID_SIGNATURE' });
    }
    authenticatedUserId = result.userId;
  }

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

  // Verify user still exists and is active
  const user = await prisma.user.findUnique({
    where: { id: authenticatedUserId },
    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 query = (request.query as Record<string, string>) ?? {};

  let userId: string | null = null;

  if (authHeader?.startsWith('Bearer ')) {
    try {
      const payload = jwt.verify(authHeader.substring(7), env.JWT_ACCESS_SECRET, { algorithms: ['HS256'] }) as TokenPayload;
      userId = payload.id;
    } catch { /* ignore */ }
  } else if (query.sig && query.exp && query.uid) {
    const result = verifyMediaSignature(request.url, query);
    if (result.valid) userId = result.userId;
  }

  if (!userId) return;

  const user = await prisma.user.findUnique({
    where: { id: userId },
    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,
    };
  }
}
