import { Injectable, Logger } from "@nestjs/common";
import {
  Organization,
  OrganizationConfiguration,
  Permission,
  Role,
  RolePermission,
} from "@tria-plc/iamapi-common";
import { DataSource, EntityManager, In } from "typeorm";

import { ERoleKey } from "@tria-plc/api-common/utils/enums/seed.enum";
import { BOOKING_RULE_ENGINE_PERMISSION_KEYS } from "./freight-permissions.registry";
import { EDR_FREIGHT_ROLES, type FreightSeedRole } from "./edr-freight.seed";

const EDR_ORG_KEY = "edr_freight";
const EDR_ORG_NAME = { en: "EDR Freight" };
const SEED_FLAG = "SEED_EDR_ORG";

type SeedOrganization = {
  id: string;
  key: string;
};

@Injectable()
export class EdrOrgSeeder {
  private readonly logger = new Logger(EdrOrgSeeder.name);

  constructor(private readonly dataSource: DataSource) {}

  async run() {
    if (!this.shouldSeed()) {
      this.logger.log(`Skipping EDR org seed because ${SEED_FLAG} is not enabled`);
      return;
    }

    await this.dataSource.transaction(async (manager) => {
      const organization = await this.ensureOrganization(manager);

      await this.ensureOrganizationConfiguration(manager, organization.id);
      await this.ensureRoles(manager, EDR_FREIGHT_ROLES);
      await this.ensureRolePermissions(manager, EDR_FREIGHT_ROLES);
      await this.ensureSuperAdminPermissions(manager);
    });

    this.logger.log(`Ensured EDR organization seed for '${EDR_ORG_KEY}'`);
  }

  private shouldSeed() {
    return process.env[SEED_FLAG]?.trim().toLowerCase() === "true";
  }

  private async ensureOrganization(
    manager: EntityManager,
  ): Promise<SeedOrganization> {
    const organizationRepository = manager.getRepository(Organization);
    let organization = await organizationRepository.findOne({
      where: { key: EDR_ORG_KEY },
      select: { id: true, key: true },
    });

    if (!organization) {
      const insertResult = await organizationRepository.insert({
        key: EDR_ORG_KEY,
        name: EDR_ORG_NAME,
        isGovernmentOrganization: true,
      });

      this.logger.log(`Seeded EDR organization '${EDR_ORG_KEY}'`);

      return {
        id: insertResult.identifiers[0]?.id as string,
        key: EDR_ORG_KEY,
      };
    }

    this.logger.log(`Ensured EDR organization '${EDR_ORG_KEY}'`);

    return {
      id: organization.id as string,
      key: EDR_ORG_KEY,
    };
  }

  private async ensureOrganizationConfiguration(
    manager: EntityManager,
    organizationId: string,
  ) {
    const organizationConfigurationRepository =
      manager.getRepository(OrganizationConfiguration);

    await organizationConfigurationRepository.upsert({
      organizationId,
      canCreateBranchByItself: true,
      canStartReceivingRecord: true,
    }, {
      conflictPaths: { organizationId: true },
    });

    this.logger.log(
      `Ensured organization configuration for '${EDR_ORG_KEY}'`,
    );
  }

  private async ensureRoles(manager: EntityManager, seedRoles: FreightSeedRole[]) {
    await manager.getRepository(Role).upsert(
      seedRoles.map(({ key, name }) => ({ key, name })),
      {
        conflictPaths: { key: true },
      },
    );

    this.logger.log(
      `Ensured EDR roles '${seedRoles.map((role) => role.key).join("', '")}'`,
    );
  }

  private async ensureRolePermissions(
    manager: EntityManager,
    seedRoles: FreightSeedRole[],
  ) {
    const permissionKeys = [...new Set(seedRoles.flatMap((role) => role.permissionKeys))];

    if (!permissionKeys.length) {
      this.logger.log("No EDR role permissions configured; skipping role-permission links");
      return;
    }

    const roleRepository = manager.getRepository(Role);
    const rolePermissionRepository = manager.getRepository(RolePermission);

    const roles = await roleRepository.find({
      where: { key: In(seedRoles.map((role) => role.key)) },
      select: { id: true, key: true },
    });
    const seededPermissions = await manager.getRepository(Permission).find({
      where: { key: In(permissionKeys) },
      select: { id: true, key: true },
    });

    const roleByKey = new Map(roles.map((role) => [role.key, role]));
    const permissionByKey = new Map(
      seededPermissions.map((permission) => [permission.key, permission]),
    );

    const rolePermissions = seedRoles.flatMap((role) => {
      const seededRole = roleByKey.get(role.key);

      if (!seededRole) {
        throw new Error(`missing_role:${role.key}`);
      }

      return role.permissionKeys.map((permissionKey) => {
        const seededPermission = permissionByKey.get(permissionKey);

        if (!seededPermission) {
          throw new Error(`missing_permission:${permissionKey}`);
        }

        return {
          roleId: seededRole.id,
          permissionId: seededPermission.id,
        };
      });
    });

    await rolePermissionRepository.upsert(rolePermissions, {
      conflictPaths: { roleId: true, permissionId: true },
    });

    this.logger.log(`Ensured ${rolePermissions.length} EDR role-permission links`);
  }

  private async ensureSuperAdminPermissions(manager: EntityManager) {
    const role = await manager.getRepository(Role).findOne({
      where: { key: ERoleKey.SUPER_ADMIN },
      select: { id: true, key: true },
    });

    if (!role) {
      this.logger.warn(
        `Role ${ERoleKey.SUPER_ADMIN} not found; skipping booking/rule-engine super_admin links`,
      );
      return;
    }

    const permissions = await manager.getRepository(Permission).find({
      where: { key: In(BOOKING_RULE_ENGINE_PERMISSION_KEYS) },
      select: { id: true, key: true },
    });

    if (!permissions.length) {
      this.logger.warn('No booking/rule-engine permissions found for super_admin');
      return;
    }

    await manager.getRepository(RolePermission).upsert(
      permissions.map((permission) => ({
        roleId: role.id,
        permissionId: permission.id,
      })),
      { conflictPaths: { roleId: true, permissionId: true } },
    );

    this.logger.log(
      `Ensured ${permissions.length} booking+rule-engine permissions on super_admin`,
    );
  }
}
