import { Injectable, Logger } from '@nestjs/common';
import { WagonStatus } from '@edr/types';
import { hashPassword } from '@tria-plc/api-common/utils/argon';
import { EUserStatus } from '@tria-plc/api-common/utils/enums/user.enum';
import {
  Employee,
  Organization,
  Role,
  User,
  UserCredential,
  UserRole,
} from '@tria-plc/iamapi-common';
import { DataSource, EntityManager } from 'typeorm';

import { Wagon } from '../modules/wagons/entities/wagon.entity';
import { WagonType } from '../modules/wagon-types/entities/wagon-type.entity';
import { ApprovalRule } from '../modules/rule-engine/entities/approval-rule.entity';
import { DEFAULT_APPROVAL_RULE_ROWS } from '../modules/rule-engine/approval-rules.defaults';

const EDR_ORG_KEY = 'edr_freight';
const MIN_WAGONS_PER_TYPE = 100;

/** The demo staff users, each mapped to a seeded freight role. */
const DEMO_STAFF_USERS = [
  { email: 'marketing@edr.local', username: 'marketing', roleKey: 'edr_marketing' },
  { email: 'operations@edr.local', username: 'operations', roleKey: 'edr_operations_officer' },
  { email: 'director@edr.local', username: 'director', roleKey: 'edr_director' },
  { email: 'ceo@edr.local', username: 'ceo', roleKey: 'edr_ceo' },
  { email: 'gl@edr.local', username: 'gl', roleKey: 'edr_global_logistics' },
] as const;

/**
 * One-shot demo data: at least 100 wagons per wagon type, the default approval
 * chains, and four staff users with distinct permissions. Every block guards on
 * an "is it already populated?" check, so this is safe to run on every boot and
 * does nothing once the data exists.
 */
@Injectable()
export class DemoFreightDataSeeder {
  private readonly logger = new Logger(DemoFreightDataSeeder.name);

  constructor(private readonly dataSource: DataSource) {}

  async run() {
    await this.dataSource.transaction(async (manager) => {
      // Demo freight data (wagons + approval rules) disabled — keep only the
      // 4 staff users. The seeders are retained for easy re-enabling; flip
      // SEED_DEMO_FREIGHT_DATA=true to run them.
      if (process.env.SEED_DEMO_FREIGHT_DATA === 'true') {
        await this.seedWagons(manager);
        await this.seedApprovalRules(manager);
      }
      await this.seedStaffUsers(manager);
    });
  }

  /** Ensure every wagon type has at least MIN_WAGONS_PER_TYPE wagons. */
  private async seedWagons(manager: EntityManager) {
    const wagonTypeRepo = manager.getRepository(WagonType);
    const wagonRepo = manager.getRepository(Wagon);

    const wagonTypes = await wagonTypeRepo.find();
    if (wagonTypes.length === 0) {
      this.logger.warn('No wagon types found; skipping wagon seed');
      return;
    }

    for (const type of wagonTypes) {
      const existing = await wagonRepo.count({ where: { wagonTypeId: type.id } });
      if (existing >= MIN_WAGONS_PER_TYPE) {
        this.logger.log(
          `Wagon type ${type.code} already has ${existing} wagons; skipping`,
        );
        continue;
      }

      const toCreate = MIN_WAGONS_PER_TYPE - existing;
      const tare = Number(type.tareWeightTons ?? 20);
      const maxPayload = Number(type.capacityTons ?? 60);
      const rows = Array.from({ length: toCreate }, (_, i) => {
        const seq = existing + i + 1;
        return wagonRepo.create({
          wagonNumber: `${type.code}-${String(seq).padStart(4, '0')}`,
          wagonTypeId: type.id,
          tareWeight: tare,
          maxPayloadWeight: maxPayload,
          status: WagonStatus.Available,
        });
      });
      await wagonRepo.save(rows);
      this.logger.log(`Seeded ${toCreate} wagons for type ${type.code}`);
    }
  }

  /** Seed the default approval chains when the table is empty. */
  private async seedApprovalRules(manager: EntityManager) {
    const repo = manager.getRepository(ApprovalRule);
    const count = await repo.count();
    if (count > 0) {
      this.logger.log(`Approval rules already populated (${count}); skipping`);
      return;
    }
    await repo.save(DEFAULT_APPROVAL_RULE_ROWS.map((row) => repo.create(row)));
    this.logger.log(`Seeded ${DEFAULT_APPROVAL_RULE_ROWS.length} approval rules`);
  }

  /** Create the four demo staff users with their roles (idempotent per email). */
  private async seedStaffUsers(manager: EntityManager) {
    const organization = await manager.getRepository(Organization).findOne({
      where: { key: EDR_ORG_KEY },
      select: { id: true, key: true },
    });
    if (!organization) {
      this.logger.warn(`Missing organization ${EDR_ORG_KEY}; skipping staff users`);
      return;
    }

    const roleRepo = manager.getRepository(Role);
    const userRepo = manager.getRepository(User);
    const credentialRepo = manager.getRepository(UserCredential);
    const userRoleRepo = manager.getRepository(UserRole);
    const employeeRepo = manager.getRepository(Employee);

    const password = process.env.DEFAULT_PASSWORD?.trim() || '12345678';
    const hashedPassword = await hashPassword(password);

    for (const staff of DEMO_STAFF_USERS) {
      const role = await roleRepo.findOne({
        where: { key: staff.roleKey },
        select: { id: true, key: true },
      });
      if (!role) {
        this.logger.warn(`Missing role ${staff.roleKey}; skipping ${staff.email}`);
        continue;
      }

      let user = await userRepo.findOne({
        where: { email: staff.email },
        select: { id: true, email: true },
      });
      if (!user) {
        user = await userRepo.save(
          userRepo.create({
            email: staff.email,
            username: staff.username,
            name: { en: staff.username },
            isActive: true,
            hasSetPassword: true,
            status: EUserStatus.ACCEPTED,
          }),
        );
        this.logger.log(`Seeded staff user ${staff.email}`);
      }

      const hasCredential = await credentialRepo.exists({
        where: { userId: user.id, isActive: true },
      });
      if (!hasCredential) {
        await credentialRepo.insert({
          userId: user.id,
          password: hashedPassword,
          isActive: true,
        });
      }

      await userRoleRepo.upsert(
        { userId: user.id, roleId: role.id, organizationId: organization.id },
        { conflictPaths: { userId: true, roleId: true } },
      );

      const hasEmployee = await employeeRepo.exists({
        where: { userId: user.id, organizationId: organization.id, isCurrent: true },
      });
      if (!hasEmployee) {
        await employeeRepo.insert({
          userId: user.id,
          organizationId: organization.id,
          isCurrent: true,
          name: { en: staff.username },
        });
      }
    }

    this.logger.log('Ensured demo staff users (marketing@, operations@, director@, ceo@)');
  }
}
