'use strict';

const bcrypt = require('bcrypt');
const config = require('../../config');

const CRUD_OPERATIONS = ['CREATE', 'READ', 'UPDATE', 'DELETE'];
const ENTITY_NAMES = [
  'users',
  'roles',
  'permissions',
  'organizations',
  'user_invitations',
  'connected_systems',
  'control_frameworks',
  'control_requirements',
  'identity_workflows',
  'evidence_models',
  'artifacts',
  'sampled_subjects',
  'proof_packets',
  'remediation_items',
  'access_reviews',
  'access_review_items',
  'exceptions',
  'audit_logs',
  'notifications',
  'organization_settings',
  'artifact_type_catalog',
  'workflow_templates',
];

const ROLE_RENAMES = {
  'Super Administrator': 'super_admin',
  Administrator: 'org_admin',
  org_governance_lead: 'compliance_manager',
  platform_ops_manager: 'security_manager',
  evidence_operations_lead: 'iam_operator',
  audit_viewer: 'auditor',
  platform_owner: 'read_only_client',
};

const SAMPLE_USER_EMAILS = {
  org_admin: 'admin@flatlogic.com',
  compliance_manager: 'john@doe.com',
  read_only_client: 'client@hello.com',
  security_manager: 'security.manager@flatlogic.com',
  iam_operator: 'iam.operator@flatlogic.com',
  auditor: 'auditor@flatlogic.com',
};

function getCrudPermissions(entityNames) {
  return entityNames.flatMap((entityName) =>
    CRUD_OPERATIONS.map((operation) => `${operation}_${entityName.toUpperCase()}`),
  );
}

function getReadPermissions(entityNames) {
  return entityNames.map((entityName) => `READ_${entityName.toUpperCase()}`);
}

module.exports = {
  async up(queryInterface, Sequelize) {
    const transaction = await queryInterface.sequelize.transaction();

    try {
      const [roleRows] = await queryInterface.sequelize.query(
        `SELECT id, name FROM "roles" WHERE "deletedAt" IS NULL`,
        { transaction },
      );

      const roleIdsByName = roleRows.reduce((accumulator, role) => {
        accumulator[role.name] = role.id;
        return accumulator;
      }, {});

      for (const [legacyName, targetName] of Object.entries(ROLE_RENAMES)) {
        const roleId = roleIdsByName[legacyName] || roleIdsByName[targetName];

        if (!roleId) {
          throw new Error(`Missing role row for '${legacyName}' -> '${targetName}' alignment.`);
        }

        await queryInterface.bulkUpdate(
          'roles',
          {
            name: targetName,
            globalAccess: targetName === 'super_admin',
            updatedAt: new Date(),
          },
          { id: roleId },
          { transaction },
        );

        roleIdsByName[targetName] = roleId;
      }

      const managedRoleNames = [
        'super_admin',
        'org_admin',
        'compliance_manager',
        'security_manager',
        'iam_operator',
        'auditor',
        'read_only_client',
      ];
      const managedRoleIds = managedRoleNames.map((roleName) => roleIdsByName[roleName]).filter(Boolean);

      const [permissionRows] = await queryInterface.sequelize.query(
        `SELECT id, name FROM "permissions" WHERE "deletedAt" IS NULL`,
        { transaction },
      );

      const permissionIdsByName = permissionRows.reduce((accumulator, permission) => {
        accumulator[permission.name] = permission.id;
        return accumulator;
      }, {});

      const permissionMatrix = {
        super_admin: [
          ...getCrudPermissions(ENTITY_NAMES),
          'READ_API_DOCS',
          'CREATE_SEARCH',
        ],
        org_admin: [
          'READ_USERS',
          'CREATE_USERS',
          'UPDATE_USERS',
          'DELETE_USERS',
          'READ_ROLES',
          'READ_PERMISSIONS',
          'READ_ORGANIZATIONS',
          'UPDATE_ORGANIZATIONS',
          'READ_USER_INVITATIONS',
          'CREATE_USER_INVITATIONS',
          'UPDATE_USER_INVITATIONS',
          'DELETE_USER_INVITATIONS',
          ...getCrudPermissions([
            'connected_systems',
            'control_frameworks',
            'control_requirements',
            'identity_workflows',
            'evidence_models',
            'artifacts',
            'sampled_subjects',
            'proof_packets',
            'remediation_items',
            'access_reviews',
            'access_review_items',
            'exceptions',
            'notifications',
            'organization_settings',
            'artifact_type_catalog',
            'workflow_templates',
          ]),
          'READ_AUDIT_LOGS',
          'READ_API_DOCS',
          'CREATE_SEARCH',
        ],
        compliance_manager: [
          ...getReadPermissions([
            'users',
            'roles',
            'organizations',
            'user_invitations',
            'connected_systems',
            'sampled_subjects',
            'exceptions',
            'audit_logs',
            'notifications',
            'organization_settings',
            'artifact_type_catalog',
            'workflow_templates',
          ]),
          ...getCrudPermissions([
            'control_frameworks',
            'control_requirements',
            'identity_workflows',
            'evidence_models',
            'proof_packets',
            'remediation_items',
            'access_reviews',
            'access_review_items',
          ]),
          'READ_ARTIFACTS',
          'UPDATE_ARTIFACTS',
          'CREATE_SEARCH',
        ],
        security_manager: [
          ...getReadPermissions([
            'users',
            'roles',
            'organizations',
            'control_frameworks',
            'control_requirements',
            'evidence_models',
            'proof_packets',
            'sampled_subjects',
            'notifications',
            'organization_settings',
            'artifact_type_catalog',
            'workflow_templates',
          ]),
          ...getCrudPermissions([
            'connected_systems',
            'remediation_items',
            'exceptions',
          ]),
          'READ_IDENTITY_WORKFLOWS',
          'UPDATE_IDENTITY_WORKFLOWS',
          'READ_ARTIFACTS',
          'UPDATE_ARTIFACTS',
          'READ_ACCESS_REVIEWS',
          'READ_ACCESS_REVIEW_ITEMS',
          'UPDATE_ACCESS_REVIEW_ITEMS',
          'READ_AUDIT_LOGS',
          'CREATE_SEARCH',
        ],
        iam_operator: [
          ...getReadPermissions([
            'users',
            'roles',
            'organizations',
            'connected_systems',
            'control_frameworks',
            'control_requirements',
            'evidence_models',
            'notifications',
            'artifact_type_catalog',
            'workflow_templates',
          ]),
          'READ_IDENTITY_WORKFLOWS',
          'UPDATE_IDENTITY_WORKFLOWS',
          'CREATE_ARTIFACTS',
          'READ_ARTIFACTS',
          'UPDATE_ARTIFACTS',
          'CREATE_SAMPLED_SUBJECTS',
          'READ_SAMPLED_SUBJECTS',
          'UPDATE_SAMPLED_SUBJECTS',
          'CREATE_PROOF_PACKETS',
          'READ_PROOF_PACKETS',
          'UPDATE_PROOF_PACKETS',
          'READ_ACCESS_REVIEWS',
          'READ_ACCESS_REVIEW_ITEMS',
          'UPDATE_ACCESS_REVIEW_ITEMS',
          'READ_EXCEPTIONS',
          'UPDATE_EXCEPTIONS',
          'CREATE_SEARCH',
        ],
        auditor: [
          ...getReadPermissions([
            'users',
            'roles',
            'organizations',
            'control_frameworks',
            'control_requirements',
            'identity_workflows',
            'evidence_models',
            'artifacts',
            'proof_packets',
            'remediation_items',
            'access_reviews',
            'access_review_items',
            'exceptions',
            'audit_logs',
            'artifact_type_catalog',
            'workflow_templates',
          ]),
          'CREATE_SEARCH',
        ],
        read_only_client: [
          ...getReadPermissions([
            'organizations',
            'organization_settings',
            'proof_packets',
            'remediation_items',
            'access_reviews',
            'access_review_items',
          ]),
          'CREATE_SEARCH',
        ],
      };

      await queryInterface.bulkDelete(
        'rolesPermissionsPermissions',
        {
          roles_permissionsId: {
            [Sequelize.Op.in]: managedRoleIds,
          },
        },
        { transaction },
      );

      const rolePermissionRows = [];
      const createdAt = new Date();
      const updatedAt = new Date();

      for (const [roleName, permissionNames] of Object.entries(permissionMatrix)) {
        const roleId = roleIdsByName[roleName];
        const uniquePermissionNames = [...new Set(permissionNames)];

        for (const permissionName of uniquePermissionNames) {
          const permissionId = permissionIdsByName[permissionName];

          if (!permissionId) {
            throw new Error(`Missing permission '${permissionName}' while aligning '${roleName}'.`);
          }

          rolePermissionRows.push({
            createdAt,
            updatedAt,
            roles_permissionsId: roleId,
            permissionId,
          });
        }
      }

      await queryInterface.bulkInsert('rolesPermissionsPermissions', rolePermissionRows, { transaction });

      const [organizationRows] = await queryInterface.sequelize.query(
        `SELECT id, name FROM "organizations" WHERE "deletedAt" IS NULL ORDER BY "createdAt" ASC`,
        { transaction },
      );

      const primaryOrganizationId = organizationRows[0]?.id || null;

      if (!primaryOrganizationId) {
        throw new Error('At least one organization is required to assign organization-scoped sample users.');
      }

      const adminHash = bcrypt.hashSync(config.admin_pass, config.bcrypt.saltRounds);
      const userHash = bcrypt.hashSync(config.user_pass, config.bcrypt.saltRounds);
      const [existingUsers] = await queryInterface.sequelize.query(
        `SELECT id, email FROM "users" WHERE "deletedAt" IS NULL`,
        { transaction },
      );
      const existingUsersByEmail = existingUsers.reduce((accumulator, user) => {
        accumulator[user.email] = user;
        return accumulator;
      }, {});

      const ensureUser = async ({ email, firstName, lastName, password, roleName }) => {
        const roleId = roleIdsByName[roleName];
        const existingUser = existingUsersByEmail[email];

        if (existingUser) {
          await queryInterface.bulkUpdate(
            'users',
            {
              firstName,
              lastName,
              emailVerified: true,
              provider: config.providers.LOCAL,
              app_roleId: roleId,
              organizationsId: roleName === 'super_admin' ? null : primaryOrganizationId,
              updatedAt: new Date(),
            },
            { id: existingUser.id },
            { transaction },
          );
          return;
        }

        await queryInterface.bulkInsert(
          'users',
          [
            {
              id: Sequelize.Utils.toDefaultValue(Sequelize.UUIDV4()),
              firstName,
              lastName,
              email,
              emailVerified: true,
              provider: config.providers.LOCAL,
              password,
              app_roleId: roleId,
              organizationsId: roleName === 'super_admin' ? null : primaryOrganizationId,
              createdAt: new Date(),
              updatedAt: new Date(),
            },
          ],
          { transaction },
        );
      };

      await ensureUser({
        email: 'super_admin@flatlogic.com',
        firstName: 'Super',
        lastName: 'Admin',
        password: adminHash,
        roleName: 'super_admin',
      });
      await ensureUser({
        email: SAMPLE_USER_EMAILS.org_admin,
        firstName: 'Organization',
        lastName: 'Admin',
        password: adminHash,
        roleName: 'org_admin',
      });
      await ensureUser({
        email: SAMPLE_USER_EMAILS.compliance_manager,
        firstName: 'Compliance',
        lastName: 'Manager',
        password: userHash,
        roleName: 'compliance_manager',
      });
      await ensureUser({
        email: SAMPLE_USER_EMAILS.security_manager,
        firstName: 'Security',
        lastName: 'Manager',
        password: userHash,
        roleName: 'security_manager',
      });
      await ensureUser({
        email: SAMPLE_USER_EMAILS.iam_operator,
        firstName: 'IAM',
        lastName: 'Operator',
        password: userHash,
        roleName: 'iam_operator',
      });
      await ensureUser({
        email: SAMPLE_USER_EMAILS.auditor,
        firstName: 'Audit',
        lastName: 'Reviewer',
        password: userHash,
        roleName: 'auditor',
      });
      await ensureUser({
        email: SAMPLE_USER_EMAILS.read_only_client,
        firstName: 'Client',
        lastName: 'Viewer',
        password: userHash,
        roleName: 'read_only_client',
      });

      await transaction.commit();
    } catch (error) {
      await transaction.rollback();
      throw error;
    }
  },

  async down() {},
};
