import { v4 as uuid } from 'uuid';
import type { CreationAttributes, QueryInterface } from 'sequelize';
import {
  ROLE_DEFINITIONS,
  ROLE_NAMES,
  type RoleName,
} from '@/shared/constants/roles';
import { SEED_ALL_USERS } from '@/shared/constants/seed-fixtures';
import {
  MODULE_READ_ALL_STAFF,
  MODULE_READ_INSTRUCTIONAL,
  MODULE_READ_PARENT_COMM,
  MODULE_READ_EXTERNAL,
  MODULE_ACTIONS,
  MODULE_PERMISSIONS,
} from '@/shared/constants/product-permissions';
import type { Roles } from '@/db/models/roles';
import type { Permissions } from '@/db/models/permissions';

/**
 * Seeds the 11 first-class roles (Workstream 3 §3.1), the permission catalog,
 * the role→permission preset matrix (§3.2), and assigns roles to the seeded
 * users. Pre-launch, no legacy: this fully replaces the old generated role set.
 *
 * Enforcement model:
 * - `super_admin` / `system_admin` are `globalAccess` and bypass per-permission
 *   checks (`middlewares/check-permissions.ts`), so they need no permission rows.
 * - `owner` / `superintendent` / `director` get every permission (full CRUD),
 *   constrained at runtime to their organization/campus by tenant scoping.
 * - `office_manager` / `teacher` / `support_staff` get read-only entity access.
 * - `student` / `guardian` / `guest` get no entity CRUD permissions (their
 *   access is the external product pages, gated on the frontend / future
 *   product-feature permissions).
 */

const PERMISSION_ENTITIES = [
  'users', 'roles', 'permissions', 'organizations', 'campuses',
  'academic_years', 'grades', 'subjects', 'staff',
  'classes', 'class_enrollments', 'class_subjects', 'timetables',
  'timetable_periods', 'attendance_sessions', 'attendance_records',
  'assessments', 'assessment_results', 'messages',
  'message_recipients', 'policy_documents',
];

const CRUD_VERBS = ['CREATE', 'READ', 'UPDATE', 'DELETE'];
const EXTRA_PERMISSIONS = ['READ_API_DOCS', 'CREATE_SEARCH'];

/** Roles granted every permission (full CRUD within their tenant/campus scope). */
const FULL_ACCESS_ROLES: readonly RoleName[] = [
  ROLE_NAMES.OWNER,
  ROLE_NAMES.SUPERINTENDENT,
  ROLE_NAMES.DIRECTOR,
];

/** Roles granted read-only access to tenant resources. */
const READ_ONLY_ROLES: readonly RoleName[] = [
  ROLE_NAMES.OFFICE_MANAGER,
  ROLE_NAMES.TEACHER,
  ROLE_NAMES.SUPPORT_STAFF,
];

/** External (non-staff) roles. */
const EXTERNAL_ROLES: readonly RoleName[] = [
  ROLE_NAMES.STUDENT,
  ROLE_NAMES.GUARDIAN,
];

// Product module/page permissions (§3.2) come from the shared single source
// `shared/constants/product-permissions.ts`, which the feature routes also use
// to enforce them — so granted names and checked names never drift.

/**
 * Per-role product-feature grants for the non-global, non-full-access roles.
 * `office_manager` excludes instructional tools and parent comms but may fill
 * attendance; `teacher` includes instructional tools + parent comms; both
 * `teacher` and `support_staff` take quizzes / leave read receipts;
 * `student`/`guardian` get only the external pages.
 */
const MODULE_PERMISSIONS_BY_ROLE: Partial<Record<RoleName, readonly string[]>> = {
  [ROLE_NAMES.OFFICE_MANAGER]: [
    ...MODULE_READ_ALL_STAFF,
    ...MODULE_READ_EXTERNAL,
    ...MODULE_ACTIONS,
    'READ_AUDIO_FILES', 'MANAGE_AUDIO_FILES',
  ],
  [ROLE_NAMES.TEACHER]: [
    ...MODULE_READ_ALL_STAFF,
    ...MODULE_READ_INSTRUCTIONAL,
    ...MODULE_READ_PARENT_COMM,
    ...MODULE_READ_EXTERNAL,
    'TAKE_QUIZ', 'ACK_READ_RECEIPT', 'ACK_POLICY',
    'READ_AUDIO_FILES', 'MANAGE_AUDIO_FILES',
  ],
  [ROLE_NAMES.SUPPORT_STAFF]: [
    ...MODULE_READ_ALL_STAFF,
    ...MODULE_READ_INSTRUCTIONAL,
    ...MODULE_READ_EXTERNAL,
    'TAKE_QUIZ', 'ACK_READ_RECEIPT', 'ACK_POLICY',
    'READ_AUDIO_FILES',
  ],
  [ROLE_NAMES.STUDENT]: [...MODULE_READ_EXTERNAL],
  [ROLE_NAMES.GUARDIAN]: [...MODULE_READ_EXTERNAL],
};

export default {
  async up(queryInterface: QueryInterface) {
    const createdAt = new Date();
    const updatedAt = new Date();

    const roleId = new Map<string, string>();
    const permId = new Map<string, string>();

    // 1. Roles.
    const roleRows: CreationAttributes<Roles>[] = ROLE_DEFINITIONS.map(
      (role) => {
        const id = uuid();
        roleId.set(role.name, id);
        return {
          id,
          name: role.name,
          scope: role.scope,
          globalAccess: role.globalAccess,
          createdAt,
          updatedAt,
        };
      },
    );
    await queryInterface.bulkInsert('roles', roleRows);

    // 2. Permissions (entity CRUD + extras + product module/page perms).
    const entityPermissionNames: string[] = [];
    for (const entity of PERMISSION_ENTITIES) {
      for (const verb of CRUD_VERBS) {
        entityPermissionNames.push(`${verb}_${entity.toUpperCase()}`);
      }
    }

    const allPermissionNames = [
      ...entityPermissionNames,
      ...EXTRA_PERMISSIONS,
      ...MODULE_PERMISSIONS,
    ];

    const permissionRows: CreationAttributes<Permissions>[] =
      allPermissionNames.map((name) => {
        const id = uuid();
        permId.set(name, id);
        return { id, name, createdAt, updatedAt };
      });
    await queryInterface.bulkInsert('permissions', permissionRows);

    // 3. Role → permission matrix.
    const links: Array<{
      createdAt: Date;
      updatedAt: Date;
      roles_permissionsId: string;
      permissionId: string;
    }> = [];

    const grant = (role: RoleName, names: readonly string[]): void => {
      const rid = roleId.get(role);
      if (!rid) return;
      for (const name of names) {
        const pid = permId.get(name);
        if (pid) {
          links.push({
            createdAt,
            updatedAt,
            roles_permissionsId: rid,
            permissionId: pid,
          });
        }
      }
    };

    const entityReadPermissions = entityPermissionNames.filter((n) =>
      n.startsWith('READ_'),
    );
    // Full-access roles get every permission (incl. director-only module pages).
    for (const role of FULL_ACCESS_ROLES) grant(role, allPermissionNames);
    // Read-only staff: read every entity + their per-role module/page perms.
    for (const role of READ_ONLY_ROLES) {
      grant(role, entityReadPermissions);
      grant(role, MODULE_PERMISSIONS_BY_ROLE[role] ?? []);
    }
    // External roles: only their external module pages.
    for (const role of EXTERNAL_ROLES) {
      grant(role, MODULE_PERMISSIONS_BY_ROLE[role] ?? []);
    }
    // Workstream 11: the office_manager also manages policy documents (the
    // director already does via full access; teacher/support stay read-only).
    grant(ROLE_NAMES.OFFICE_MANAGER, [
      'CREATE_POLICY_DOCUMENTS',
      'UPDATE_POLICY_DOCUMENTS',
      'DELETE_POLICY_DOCUMENTS',
    ]);

    await queryInterface.bulkInsert('rolesPermissionsPermissions', links);

    // 4. Assign roles to the seeded fixture users (by id — robust to the
    //    configured super-admin email).
    for (const fixture of SEED_ALL_USERS) {
      const rid = roleId.get(fixture.role);
      if (!rid) continue;
      await queryInterface.sequelize.query(
        `UPDATE "users" SET "app_roleId"=:rid WHERE "id"=:id`,
        { replacements: { rid, id: fixture.id } },
      );
    }
  },

  async down(queryInterface: QueryInterface) {
    await queryInterface.bulkDelete('rolesPermissionsPermissions', {});
    await queryInterface.bulkDelete('permissions', {});
    await queryInterface.bulkDelete('roles', {});
  },
};
