import { AbilityBuilder, PureAbility, AbilityClass, ExtractSubjectType, InferSubjects } from '@casl/ability';
import { Injectable } from '@nestjs/common';
import { Role, User, VIP, Driver, ScheduleEvent, Flight, Vehicle } from '@prisma/client';

/**
 * Define all possible actions in the system
 */
export enum Action {
  Manage = 'manage', // Special: allows everything
  Create = 'create',
  Read = 'read',
  Update = 'update',
  Delete = 'delete',
  Approve = 'approve', // Special: for user approval
  UpdateStatus = 'update-status', // Special: for drivers to update event status
}

/**
 * Define all subjects (resources) in the system
 */
export type Subjects =
  | 'User'
  | 'VIP'
  | 'Driver'
  | 'ScheduleEvent'
  | 'Flight'
  | 'Vehicle'
  | 'Settings'
  | 'all';

/**
 * Define the AppAbility type
 */
export type AppAbility = PureAbility<[Action, Subjects]>;

@Injectable()
export class AbilityFactory {
  /**
   * Define abilities for a user based on their role
   */
  defineAbilitiesFor(user: User): AppAbility {
    const { can, cannot, build } = new AbilityBuilder<AppAbility>(
      PureAbility as AbilityClass<AppAbility>,
    );

    // Define permissions based on role
    if (user.role === Role.ADMINISTRATOR) {
      // Administrators can do everything
      can(Action.Manage, 'all');
    } else if (user.role === Role.COORDINATOR) {
      // Coordinators have full access except user management
      can(Action.Read, ['VIP', 'Driver', 'ScheduleEvent', 'Flight', 'Vehicle']);
      can(Action.Create, ['VIP', 'Driver', 'ScheduleEvent', 'Flight', 'Vehicle']);
      can(Action.Update, ['VIP', 'Driver', 'ScheduleEvent', 'Flight', 'Vehicle']);
      can(Action.Delete, ['VIP', 'Driver', 'ScheduleEvent', 'Flight', 'Vehicle']);

      // Cannot manage users
      cannot(Action.Create, 'User');
      cannot(Action.Update, 'User');
      cannot(Action.Delete, 'User');
      cannot(Action.Approve, 'User');
    } else if (user.role === Role.DRIVER) {
      // Drivers can only read most resources
      can(Action.Read, ['VIP', 'Driver', 'ScheduleEvent', 'Vehicle']);

      // Drivers can update status of events (driver relationship checked in guard)
      can(Action.UpdateStatus, 'ScheduleEvent');

      // Cannot access flights
      cannot(Action.Read, 'Flight');

      // Cannot access users
      cannot(Action.Read, 'User');
    }

    return build({
      // Detect subject type from string
      detectSubjectType: (item) => item as ExtractSubjectType<Subjects>,
    });
  }

  /**
   * Check if user can perform action on subject
   */
  canUserPerform(user: User, action: Action, subject: Subjects): boolean {
    const ability = this.defineAbilitiesFor(user);
    return ability.can(action, subject);
  }
}
