// src/server/shared/lib/auth.ts
// Authentication and authorization utilities

import { FastifyRequest, FastifyReply } from 'fastify';
import { dayjs } from './dayjs';
import { UserRole } from '@shared/constants/roles';

// Re-export UserRole for backward compatibility
export type { UserRole };

// User information from JWT
export interface UserInfo {
  id: string;
  username: string;
  role: UserRole;
}

// Custom error class for authorization errors
export class AuthorizationError extends Error {
  statusCode: number;
  code?: string;

  constructor(message: string = 'Insufficient permissions', statusCode: number = 403, code?: string) {
    super(message);
    this.name = 'AuthorizationError';
    this.statusCode = statusCode;
    this.code = code;
  }
}

// Error code for disabled/fired user account
export const ACCOUNT_DISABLED_ERROR_CODE = 'ACCOUNT_DISABLED';

// Interface for account access check
export interface AccountAccessCheck {
  createdAt: Date;
  archivedAt?: Date | null;
  isFired: boolean;
}

// Helper function to check if user account access is allowed
// Returns true if access is allowed, false if account is disabled
export function isAccountAccessAllowed(user: AccountAccessCheck): boolean {
  // Get current time in Moscow timezone
  const mskNow = dayjs().tz('Europe/Moscow');

  // Requirement 1: Future Hire - Deny access if current time is before the start of the createdAt day in MSK
  if (mskNow.isBefore(dayjs.utc(user.createdAt).tz('Europe/Moscow').startOf('day'))) {
    return false;
  }

  // Requirement 2: Termination - If user is fired, deny access if current time is on or after the start of the archivedAt day in MSK
  if (user.isFired && user.archivedAt) {
    if (mskNow.isSameOrAfter(dayjs.utc(user.archivedAt).tz('Europe/Moscow').startOf('day'))) {
      return false;
    }
  }

  // If user is fired but no archivedAt is set, deny access
  if (user.isFired && !user.archivedAt) {
    return false;
  }

  // Access is allowed
  return true;
}

// Middleware factory for authentication-only access control
export function requireAuth() {
  return async (request: FastifyRequest, reply: FastifyReply): Promise<void> => {
    if (!request.user) {
      try {
        await request.jwtVerify();
      } catch (err) {
        throw new AuthorizationError('Unauthorized access', 401);
      }
    }
    return;
  };
}

// Middleware factory for role-based access control
export function requireRole(...allowedRoles: UserRole[]) {
  return async (request: FastifyRequest, reply: FastifyReply): Promise<void> => {
    // Automatically verify JWT token from HttpOnly Cookie
    if (!request.user) {
      try {
        await request.jwtVerify();
      } catch (err) {
        throw new AuthorizationError('Unauthorized access', 401);
      }
    }

    // Get user from JWT payload (set by JWT plugin)
    const user = request.user as UserInfo | undefined;

    // Check if user is authenticated
    if (!user) {
      throw new AuthorizationError('Authentication required', 401);
    }

    // GOD role bypasses all role checks
    if (user.role === 'GOD') return;

    // Check if user has required role
    if (!allowedRoles.includes(user.role)) {
      throw new AuthorizationError(
        `Insufficient permissions. Required roles: ${allowedRoles.join(', ')}`,
        403
      );
    }

    // User is authorized, proceed to the route handler
    // Явный возврат из async функции разрешает запрос
    return;
  };
}

// Helper function to check if user has specific role
export function hasRole(user: UserInfo | undefined, role: UserRole): boolean {
  // GOD role has all roles
  if (user?.role === 'GOD') return true;
  return user?.role === role;
}

// Helper function to check if user has any of the specified roles
export function hasAnyRole(
  user: UserInfo | undefined,
  roles: UserRole[]
): boolean {
  // GOD role has all roles
  if (user?.role === 'GOD') return true;
  return user ? roles.includes(user.role) : false;
}

// Helper function to check if user is admin
export function isAdmin(user: UserInfo | undefined): boolean {
  return hasRole(user, 'ADMIN');
}

