import {
  CanActivate,
  ExecutionContext,
  Injectable,
  UnauthorizedException,
  ForbiddenException,
} from '@nestjs/common';
import { Reflector } from '@nestjs/core';
import type { Request } from 'express';
import { IS_PUBLIC_KEY } from '../common/public.decorator';
import { AuthService } from './auth.service';
import type { AuthUserContext } from '../common/request-context';

const COOKIE_NAME = 'tms_session';

type ReqWithAuth = Request & {
  authUser?: AuthUserContext;
  cookies?: Record<string, string | undefined>;
};

@Injectable()
export class SessionGuard implements CanActivate {
  constructor(
    private readonly reflector: Reflector,
    private readonly auth: AuthService,
  ) {}

  async canActivate(context: ExecutionContext): Promise<boolean> {
    const isPublic = this.reflector.getAllAndOverride<boolean>(IS_PUBLIC_KEY, [
      context.getHandler(),
      context.getClass(),
    ]);

    const req = context.switchToHttp().getRequest<ReqWithAuth>();
    const token = req.cookies?.[COOKIE_NAME];
    const resolved = await this.auth.resolveSession(token);

    if (resolved) {
      const u = resolved.sessionUser;
      req.authUser = {
        id: u.id,
        organizationId: u.organizationId,
        username: u.username,
        fullName: u.fullName,
        email: u.email ?? null,
        isSuperAdmin: u.isSuperAdmin,
        mustResetPassword: u.mustResetPassword,
        permissions: u.permissions,
        roles: u.roles,
        sessionId: resolved.sessionId,
      };
    }

    if (isPublic) return true;
    if (!resolved) throw new UnauthorizedException('Authentication required');
    // A provisioned/reset credential is valid only to inspect identity, rotate
    // the password, or end the session. Keep this here (before permission
    // guards) so a stale permission cache can never bypass the restriction.
    if (resolved.sessionUser.mustResetPassword) {
      // Depending on the Express adapter/global API prefix, req.path can be
      // either `/auth/me` or `/api/v1/auth/me`.
      const path = req.path.replace(/\/+$/, '').replace(/^.*(\/auth\/)/, '/auth/');
      if (!['/auth/me', '/auth/change-password', '/auth/logout'].includes(path)) {
        throw new ForbiddenException('Password must be changed before continuing');
      }
    }
    return true;
  }
}