import { Injectable, Logger } from '@nestjs/common';
import {
  Organization,
  Permission,
  Position,
  PositionPermission,
  Unit,
} from '@tria-plc/iamapi-common';
import { DataSource, EntityManager, In } from 'typeorm';

import { EDR_FREIGHT_POSITIONS } from './edr-freight.seed';

const SEED_FLAG = 'SEED_EDR_ORG';
const EDR_ORG_KEY = 'edr_freight';
const EDR_UNIT_KEY = 'edr_freight_app';

/**
 * Seeds the operational freight positions (CEO, Chief, Director, Marketer,
 * Operation, Ethiopian GL, Djibouti GL) as Position + PositionPermission rows
 * on the `edr_freight_app` unit. Positions-as-roles: users get their freight
 * access by being assigned to a Position (via EmployeePosition), and the
 * position's PositionPermission grants come from EDR_FREIGHT_POSITIONS.
 *
 * Gated behind the same SEED_EDR_ORG flag as EdrOrgSeeder and depends on the
 * org/unit/permission catalog it seeds, so it must run AFTER EdrOrgSeeder.
 * Idempotent: positions upsert by (key, unitId); grants insert only the
 * permission ids a position is still missing.
 */
@Injectable()
export class FreightPositionsSeeder {
  private readonly logger = new Logger(FreightPositionsSeeder.name);

  constructor(private readonly dataSource: DataSource) {}

  async run() {
    if (process.env[SEED_FLAG]?.trim().toLowerCase() !== 'true') {
      this.logger.log(
        `Skipping freight positions seed because ${SEED_FLAG} is not enabled`,
      );
      return;
    }

    await this.dataSource.transaction(async (manager) => {
      const organization = await manager.getRepository(Organization).findOne({
        where: { key: EDR_ORG_KEY },
        select: { id: true },
      });

      if (!organization) {
        throw new Error(`missing_organization:${EDR_ORG_KEY}`);
      }

      const unit = await manager.getRepository(Unit).findOne({
        where: { key: EDR_UNIT_KEY, organizationId: organization.id },
        select: { id: true },
      });

      if (!unit) {
        throw new Error(`missing_unit:${EDR_UNIT_KEY}`);
      }

      const permissionKeyToId = await this.loadPermissionIds(manager);

      for (const seed of EDR_FREIGHT_POSITIONS) {
        const positionId = await this.ensurePosition(
          manager,
          seed,
          unit.id as string,
          organization.id as string,
        );

        await this.ensurePositionPermissions(
          manager,
          positionId,
          seed,
          permissionKeyToId,
        );
      }
    });

    this.logger.log(
      `Ensured ${EDR_FREIGHT_POSITIONS.length} freight positions on unit '${EDR_UNIT_KEY}'`,
    );
  }

  /** Resolve every permission key referenced by any position to its id. */
  private async loadPermissionIds(
    manager: EntityManager,
  ): Promise<Map<string, string>> {
    const keys = [
      ...new Set(EDR_FREIGHT_POSITIONS.flatMap((p) => p.permissionKeys)),
    ];

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

    const map = new Map(permissions.map((p) => [p.key, p.id as string]));

    const missing = keys.filter((key) => !map.has(key));
    if (missing.length > 0) {
      throw new Error(`missing_permissions:${missing.join(',')}`);
    }

    return map;
  }

  private async ensurePosition(
    manager: EntityManager,
    seed: (typeof EDR_FREIGHT_POSITIONS)[number],
    unitId: string,
    organizationId: string,
  ): Promise<string> {
    const positionRepository = manager.getRepository(Position);

    const existing = await positionRepository.findOne({
      where: { key: seed.key, unitId },
      select: { id: true },
    });

    if (existing) {
      return existing.id as string;
    }

    const inserted = await positionRepository.insert({
      key: seed.key,
      name: { ...seed.name },
      rank: seed.rank,
      unitId,
      organizationId,
    });

    this.logger.log(`Seeded freight position '${seed.key}'`);

    return inserted.identifiers[0]?.id as string;
  }

  private async ensurePositionPermissions(
    manager: EntityManager,
    positionId: string,
    seed: (typeof EDR_FREIGHT_POSITIONS)[number],
    permissionKeyToId: Map<string, string>,
  ) {
    const positionPermissionRepository =
      manager.getRepository(PositionPermission);

    const existing = await positionPermissionRepository.find({
      where: { positionId },
      select: { permissionId: true },
    });
    const existingPermissionIds = new Set(
      existing.map((row) => row.permissionId),
    );

    const rowsToInsert = seed.permissionKeys
      .map((key) => permissionKeyToId.get(key) as string)
      .filter((permissionId) => !existingPermissionIds.has(permissionId))
      .map((permissionId) => ({ positionId, permissionId }));

    if (rowsToInsert.length === 0) {
      return;
    }

    await positionPermissionRepository.insert(rowsToInsert);

    this.logger.log(
      `Granted ${rowsToInsert.length} permissions to position '${seed.key}'`,
    );
  }
}
