import { Entity, Column, ManyToOne, JoinColumn, Unique } from 'typeorm';
import { BaseEntity } from '../database/base.entity';
import { RoleEntity } from './role.entity';

/**
 * Access levels per module.
 * Per CLAUDE.md line 75: None / View / Create / Edit / Delete
 */
export enum AccessLevel {
  NONE = 'none',
  VIEW = 'view',
  CREATE = 'create',
  EDIT = 'edit',
  DELETE = 'delete',
}

/**
 * Module names matching Phase 1 scope.
 * Per scope doc: 14 modules in Phase 1.
 *
 * NOTE: This is NOT a hardcoded role check — it's a reference list
 * of modules that permissions are assigned against.
 */
/**
 * Module order mirrors the sidebar (AppLayout.tsx). Each sidebar sub-menu
 * maps to its own module row in the Roles → Permissions dialog, except
 * the Settings group which stays unified under one SETTINGS module.
 */
export enum ModuleName {
  // Queries
  QUERY = 'query',
  // Bookings group
  BOOKING = 'booking',
  // Approval authority for booking change requests (on-trip itinerary
  // edits, zero-value services). EDIT = can approve/reject/hold.
  // VIEW = can see pending queue but not decide. NONE = no access.
  BOOKING_APPROVAL = 'booking_approval',
  /** Full-bypass switch — users with VIEW on this module skip ALL
   *  approval triggers on booking create/update (same-day, next-day,
   *  post-confirm-edit, floor, zero). Seeded on Admin role only in
   *  1783800; ops can grant/revoke to any role from Settings → Roles
   *  without touching code. Explicitly SEPARATE from BOOKING_APPROVAL
   *  (which controls "can decide CRs") so an Ops Manager can still
   *  approve requests while their own saves get flagged for review. */
  BOOKING_APPROVAL_BYPASS = 'booking_approval_bypass',
  DRIVER_ASSIGNMENT = 'driver_assignment',
  // Split-axis edit permissions for the Movement Dashboard (added
  // 2026-05-13). DRIVER_ASSIGNMENT controls VIEW; these two control
  // EDIT on transport-side vs activity-side rows respectively. Lets
  // ops have a Transport Manager who can reassign drivers/vehicles
  // but can't touch activity suppliers, and an Activity Manager with
  // the inverse permission. Admin + Operations Manager get EDIT on
  // both by default.
  TRANSPORT_ASSIGNMENT = 'transport_assignment',
  ACTIVITY_ASSIGNMENT = 'activity_assignment',
  MOVEMENT_CHART = 'movement_chart',
  PAYMENT_SETTLEMENT = 'payment_settlement',
  // MD Payment Collection — driver / supplier collects cash from guest
  // at the service. EDIT gates marking a service as requiring
  // collection AND confirming the fresh collection. DELETE additionally
  // gates editing an already-CONFIRMED collection row (audit-locked).
  // View is implied by DRIVER_ASSIGNMENT: view (the row is already on
  // the Movement Chart).
  PAYMENT_COLLECTION = 'payment_collection',
  // Supplier Collection — read-only rollup of cash-at-service collections
  // grouped by supplier (and driver in the modal). VIEW gates the listing
  // page + GET /by-supplier analytics endpoints. Separate from
  // PAYMENT_COLLECTION so finance/ops can see totals without MD edit
  // rights on mark-required / confirm / amend flows.
  SUPPLIER_COLLECTION = 'supplier_collection',
  // Pricing group
  TRANSPORT_PRICING = 'transport_pricing',
  ACTIVITY_PRICING = 'activity_pricing',
  // Floor-price (minimum) access — split from the main pricing modules
  // so finance can grant ops staff selling-price edit without exposing
  // the floor margins. VIEW = can see floor columns in UI + Excel.
  // EDIT = can change floor values. Without VIEW, floor inputs are
  // hidden everywhere AND the Excel sample/import drops those columns.
  TRANSPORT_FLOOR_PRICING = 'transport_floor_pricing',
  ACTIVITY_FLOOR_PRICING = 'activity_floor_pricing',
  // Services group
  HOTEL = 'hotel',
  TRANSPORTATION = 'transportation',
  ACTIVITY = 'activity',
  // Masters group
  SUPPLIER = 'supplier',
  DRIVER = 'driver',
  VEHICLE = 'vehicle',
  // Trip Source (agents) — moved out of the Settings group into Masters
  // and given its own permission module (2026-10-10) so agent access can
  // be granted independently of the rest of Settings. The lookup endpoint
  // stays @Lookup() (any authed user) for the Query/Booking Source picker;
  // only the admin list/CRUD is gated by this module.
  TRIP_SOURCE = 'trip_source',
  // Masters moved out of Settings (2026-10-15) — each gets its own
  // module so Settings keeps only tenant administration. Default:
  // Admin full, every other role VIEW (tenant.service backfill).
  DESTINATION = 'destination',
  TAX_TYPE = 'tax_type',
  VEHICLE_TYPE = 'vehicle_type',
  DESTINATION_VEHICLE_TYPE = 'destination_vehicle_type',
  BANK = 'bank',
  BANK_ACCOUNT = 'bank_account',
  // Activity Ticket Inventory — batch purchase + per-ticket tracking
  ACTIVITY_TICKET = 'activity_ticket',
  // Settings group — General, Seasons, Service Types, Currencies,
  // Exchange Rates, Users, Roles, Role Hierarchy, Audit Log. Trip
  // Sources (2026-10-10) and the other masters (2026-10-15) have their
  // own modules above.
  SETTINGS = 'settings',
  // Accounting group — Tax ledger + future reports (P&L, supplier
  // payables roll-up). Read-only for now; VIEW gates the entire tab,
  // EDIT reserved for adjustments down the road.
  ACCOUNTING = 'accounting',
  // Communication Templates — tenant-configurable Email + WhatsApp
  // template master (Settings → Templates). VIEW gates the admin
  // page + the FE Copy Email/WhatsApp buttons on Query / Booking /
  // MD; EDIT / CREATE / DELETE gate mutations on the admin page.
  COMMUNICATION_TEMPLATE = 'communication_template',
  // Impersonate ("View as user") — read-only app-wide impersonation.
  // VIEW = may start a read-only view-as session for another user in
  // the same tenant (sees their menu, screens + scoped data; cannot
  // save anything — all writes are blocked server-side while in
  // view-as). Defaults to NONE for every role (off) so it must be
  // explicitly granted per role in Settings → Roles. Added 2026-10-10.
  IMPERSONATE = 'impersonate',
}

/**
 * Masters split out of Settings (2026-10-15). Default access when the
 * permission row is first created: Admin keeps its full default; every
 * other role — seeded or custom — gets VIEW (client decision), so the
 * moved pages stay readable without granting edit rights.
 */
export const MASTER_MODULES_VIEW_DEFAULT: ReadonlySet<string> = new Set<string>([
  ModuleName.DESTINATION,
  ModuleName.TAX_TYPE,
  ModuleName.VEHICLE_TYPE,
  ModuleName.DESTINATION_VEHICLE_TYPE,
  ModuleName.BANK,
  ModuleName.BANK_ACCOUNT,
]);

/**
 * RolePermission entity — maps Role × Module → AccessLevel.
 *
 * Per CLAUDE.md:
 * - All permission checks via DB/Redis only
 * - Access levels per module: None / View / Create / Edit / Delete
 * - Permissions cached in Redis → invalidate on any permission change
 */
@Entity('role_permissions')
@Unique(['role', 'module'])
export class RolePermissionEntity extends BaseEntity {
  @ManyToOne(() => RoleEntity, (role) => role.permissions, {
    onDelete: 'CASCADE',
  })
  @JoinColumn({ name: 'role_id' })
  role: RoleEntity;

  @Column({ type: 'varchar', length: 100 })
  module: string; // ModuleName value

  @Column({ type: 'enum', enum: AccessLevel, default: AccessLevel.NONE })
  access_level: AccessLevel;
}
