import { Injectable, Logger } from '@nestjs/common';
import {
  Application,
  Organization,
  OrganizationConfiguration,
  Permission,
  Role,
  RolePermission,
} from '@tria-plc/iamapi-common';
import { InjectDataSource } from '@nestjs/typeorm';
import { DataSource, EntityManager, In } from 'typeorm';
import { ERoleKey } from '@tria-plc/api-common/utils/enums/seed.enum';
import {
  PASSENGER_PERMISSIONS,
  PASSENGER_PERMISSION_KEYS,
} from './passenger-permissions.registry';
import { EDR_PASSENGER_APPLICATION, EDR_PASSENGER_ROLES, type PassengerSeedRole } from './edr-passenger.seed';

const EDR_ORG_KEY = 'edr';
const EDR_ORG_NAME = { am: 'EDR', en: 'EDR' };
const SEED_FLAG = 'SEED_EDR_PASSENGER_ORG';

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

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

  constructor(@InjectDataSource() private readonly dataSource: DataSource) {}

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

    await this.dataSource.transaction(async (manager) => {
      await this.ensureApplication(manager);
      await this.ensurePermissions(manager);
      const organization = await this.ensureOrganization(manager);
      await this.ensureOrganizationConfiguration(manager, organization.id);
      await this.ensureRoles(manager, EDR_PASSENGER_ROLES);
      await this.ensureRolePermissions(manager, EDR_PASSENGER_ROLES);
      await this.ensureSuperAdminPermissions(manager);
      await this.ensurePositions(manager, organization.id);
    });

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

  private async ensureApplication(manager: EntityManager) {
    await manager.getRepository(Application).upsert(
      {
        id: EDR_PASSENGER_APPLICATION.id,
        key: EDR_PASSENGER_APPLICATION.key,
        name: EDR_PASSENGER_APPLICATION.name,
      },
      { conflictPaths: { key: true } },
    );
    this.logger.log(`Ensured application '${EDR_PASSENGER_APPLICATION.key}'`);
  }

  private async ensurePermissions(manager: EntityManager) {
    await manager.getRepository(Permission).upsert(
      PASSENGER_PERMISSIONS.map((p) => ({
        id: p.id,
        key: p.key,
        name: p.name,
        applicationId: EDR_PASSENGER_APPLICATION.id,
      })),
      { conflictPaths: { key: true } },
    );
    this.logger.log(`Ensured ${PASSENGER_PERMISSIONS.length} passenger permissions`);
  }

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

    if (!org) {
      const result = await repo.insert({
        key: EDR_ORG_KEY,
        name: EDR_ORG_NAME,
        isGovernmentOrganization: true,
      });
      this.logger.log(`Seeded EDR passenger organization '${EDR_ORG_KEY}'`);
      return { id: result.identifiers[0]?.id as string, key: EDR_ORG_KEY };
    }

    this.logger.log(`Ensured EDR passenger organization '${EDR_ORG_KEY}'`);
    return { id: org.id as string, key: EDR_ORG_KEY };
  }

  private async ensureOrganizationConfiguration(manager: EntityManager, organizationId: string) {
    await manager.getRepository(OrganizationConfiguration).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: PassengerSeedRole[]) {
    await manager.getRepository(Role).upsert(
      seedRoles.map(({ key, name }) => ({ key, name })),
      { conflictPaths: { key: true } },
    );
    this.logger.log(`Ensured passenger roles: ${seedRoles.map((r) => r.key).join(', ')}`);
  }

  private async ensureRolePermissions(manager: EntityManager, seedRoles: PassengerSeedRole[]) {
    const allPermissionKeys = [...new Set(seedRoles.flatMap((r) => r.permissionKeys))];
    if (!allPermissionKeys.length) return;

    const roles = await manager.getRepository(Role).find({
      where: { key: In(seedRoles.map((r) => r.key)) },
      select: { id: true, key: true },
    });
    const permissions = await manager.getRepository(Permission).find({
      where: { key: In(allPermissionKeys) },
      select: { id: true, key: true },
    });

    const roleByKey = new Map(roles.map((r) => [r.key, r]));
    const permByKey = new Map(permissions.map((p) => [p.key, p]));

    let totalUpserted = 0;
    let totalPruned = 0;

    for (const seedRole of seedRoles) {
      const role = roleByKey.get(seedRole.key);
      if (!role) throw new Error(`missing_role:${seedRole.key}`);

      const desiredPermissionIds = new Set(
        seedRole.permissionKeys.map((key) => {
          const perm = permByKey.get(key);
          if (!perm) throw new Error(`missing_permission:${key}`);
          return perm.id as string;
        }),
      );

      // Remove links that are no longer in this role's preset
      const existing = await manager.getRepository(RolePermission).find({
        where: { roleId: role.id as string },
        select: { permissionId: true },
      });
      const toRemove = existing
        .map((rp) => rp.permissionId as string)
        .filter((permId) => !desiredPermissionIds.has(permId));

      if (toRemove.length > 0) {
        await manager.getRepository(RolePermission).delete(
          toRemove.map((permissionId) => ({ roleId: role.id as string, permissionId })),
        );
        totalPruned += toRemove.length;
      }

      // Upsert the full desired set
      const links = [...desiredPermissionIds].map((permissionId) => ({
        roleId: role.id as string,
        permissionId,
      }));
      await manager.getRepository(RolePermission).upsert(links, {
        conflictPaths: { roleId: true, permissionId: true },
      });
      totalUpserted += links.length;
    }

    this.logger.log(
      `Synced passenger role-permission links: ${totalUpserted} upserted, ${totalPruned} pruned`,
    );
  }

  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 super_admin permission links`);
      return;
    }

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

    if (!permissions.length) return;

    await manager.getRepository(RolePermission).upsert(
      permissions.map((p) => ({ roleId: role.id, permissionId: p.id })),
      { conflictPaths: { roleId: true, permissionId: true } },
    );
    this.logger.log(`Ensured ${permissions.length} passenger permissions on super_admin`);
  }

  private async ensurePositions(manager: EntityManager, organizationId: string) {
    const positions = [
      { key: 'edr_passenger_director',           name: { en: 'Director',                 am: 'ዳይሬክተር' },              rank: 1 },
      { key: 'edr_passenger_finance_manager',    name: { en: 'Finance Manager',          am: 'የፋይናንስ ሥራ አስኪያጅ' },    rank: 2 },
      { key: 'edr_passenger_finance_officer',    name: { en: 'Finance Officer',          am: 'የፋይናንስ ኦፊሰር' },        rank: 3 },
      { key: 'edr_passenger_team_leader',        name: { en: 'Team Leader',              am: 'ቡድን መሪ' },              rank: 4 },
      { key: 'edr_passenger_station_master',     name: { en: 'Station Master',           am: 'ጣቢያ ሃላፊ' },            rank: 5 },
      { key: 'edr_passenger_station_supervisor', name: { en: 'Station Supervisor',       am: 'ጣቢያ ተቆጣጣሪ' },          rank: 6 },
      { key: 'edr_passenger_ticket_officer',     name: { en: 'Passenger Ticket Officer', am: 'የተሳፋሪ ቲኬት ኦፊሰር' },     rank: 7 },
      { key: 'edr_passenger_operational_staff',  name: { en: 'Operational Staff',        am: 'ስራ ሰራተኛ' },            rank: 8 },
    ];

    let created = 0;
    for (const pos of positions) {
      const existing = await manager.query<Array<{ id: string }>>(
        `SELECT id FROM iam.positions WHERE key = $1 AND organization_id = $2 LIMIT 1`,
        [pos.key, organizationId],
      );
      if (existing.length === 0) {
        await manager.query(
          `INSERT INTO iam.positions (id, name, key, rank, organization_id, unit_id, position_type_id, created_at, updated_at)
           VALUES (gen_random_uuid(), $1::jsonb, $2, $3, $4::uuid, NULL, NULL, NOW(), NOW())`,
          [JSON.stringify(pos.name), pos.key, pos.rank, organizationId],
        );
        created++;
      }
    }
    this.logger.log(`Ensured ${positions.length} passenger positions (${created} newly created)`);
  }
}
