import { Injectable, Logger } from "@nestjs/common";
import { hashPassword } from "@tria-plc/api-common/utils/argon";
import { EUserStatus } from "@tria-plc/api-common/utils/enums/user.enum";
import { ERoleKey } from "@tria-plc/api-common/utils/enums/seed.enum";
import {
  Employee,
  Organization,
  Permission,
  Role,
  RolePermission,
  User,
  UserCredential,
  UserRole,
} from "@tria-plc/iamapi-common";
import { DataSource } from "typeorm";

const SEED_FLAG = "SEED_DEMO_USERS";

const DEMO_ORG_KEY = "demo_iam";
const DEMO_ORG_NAME = { en: "Demo IAM" };

const DEMO_PERMISSIONS = [
  { key: "can:demo:user1", name: { en: "Can access demo user1" } },
  { key: "can:demo:user2", name: { en: "Can access demo user2" } },
];

const DEMO_ROLES = [
  { key: "demo_user1", name: { en: "Demo User1" } },
  { key: "demo_user2", name: { en: "Demo User2" } },
];

const DEMO_USERS = [
  {
    email: "user@gmail.com",
    username: "user",
    name: { en: "Demo User 1" },
    roleKey: "demo_user1",
  },
  {
    email: "user2@gmail.com",
    username: "user2",
    name: { en: "Demo User 2" },
    roleKey: "demo_user2",
  },
];

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

  constructor(private readonly dataSource: DataSource) {}

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

    await this.dataSource.transaction(async (manager) => {
      const organizationRepository = manager.getRepository(Organization);
      const employeeRepository = manager.getRepository(Employee);
      const permissionRepository = manager.getRepository(Permission);
      const roleRepository = manager.getRepository(Role);
      const rolePermissionRepository = manager.getRepository(RolePermission);
      const userRepository = manager.getRepository(User);
      const userCredentialRepository = manager.getRepository(UserCredential);
      const userRoleRepository = manager.getRepository(UserRole);

      await organizationRepository.upsert(
        {
          key: DEMO_ORG_KEY,
          name: DEMO_ORG_NAME,
          // status defaults to ACTIVE in IAM entity
          isGovernmentOrganization: true,
        },
        { conflictPaths: { key: true } },
      );

      const organization = await organizationRepository.findOne({
        where: { key: DEMO_ORG_KEY },
        select: { id: true, key: true },
      });

      if (!organization) {
        throw new Error("demo_org_seed_failed");
      }

      await permissionRepository.upsert(DEMO_PERMISSIONS, {
        conflictPaths: { key: true },
      });

      await roleRepository.upsert(DEMO_ROLES, {
        conflictPaths: { key: true },
      });

      const roles = await roleRepository.find({ where: DEMO_ROLES.map((r) => ({ key: r.key })) });
      const permissions = await permissionRepository.find({
        where: DEMO_PERMISSIONS.map((p) => ({ key: p.key })),
      });

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

      const superAdminRole = await roleRepository.findOne({
        where: { key: ERoleKey.SUPER_ADMIN },
        select: { id: true, key: true },
      });

      const rolePermissionsToUpsert = [
        {
          roleId: roleByKey.get("demo_user1")!.id,
          permissionId: permissionByKey.get("can:demo:user1")!.id,
        },
        {
          roleId: roleByKey.get("demo_user2")!.id,
          permissionId: permissionByKey.get("can:demo:user2")!.id,
        },
        ...(superAdminRole
          ? ([
              {
                roleId: superAdminRole.id,
                permissionId: permissionByKey.get("can:demo:user1")!.id,
              },
              {
                roleId: superAdminRole.id,
                permissionId: permissionByKey.get("can:demo:user2")!.id,
              },
            ] as Array<{ roleId: string; permissionId: string }>)
          : []),
      ];

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

      const hashedPassword = await hashPassword("12345678");

      for (const demoUser of DEMO_USERS) {
        const existingUser = await userRepository.findOne({
          where: { email: demoUser.email },
          select: { id: true, email: true },
        });

        let user = existingUser;
        if (!user) {
          user = await userRepository.save(
            userRepository.create({
              email: demoUser.email,
              username: demoUser.username,
              name: demoUser.name,
              isActive: true,
              hasSetPassword: true,
              status: EUserStatus.ACCEPTED,
            }),
          );
        }

        // Ensure an active credential exists for login.
        const activeCredentialExists = await userCredentialRepository.exists({
          where: {
            userId: user.id,
            isActive: true,
          },
        });

        if (!activeCredentialExists) {
          await userCredentialRepository.insert({
            userId: user.id,
            password: hashedPassword,
            isActive: true,
          });
        }

        // Login query requires a current employee in an ACTIVE organization.
        const employeeExists = await employeeRepository.exists({
          where: {
            userId: user.id,
            organizationId: organization.id,
            isCurrent: true,
          },
        });

        if (!employeeExists) {
          await employeeRepository.insert({
            userId: user.id,
            organizationId: organization.id,
            isCurrent: true,
            name: demoUser.name,
          });
        }

        const role = roleByKey.get(demoUser.roleKey);
        if (!role) {
          throw new Error(`missing_role:${demoUser.roleKey}`);
        }

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

    this.logger.log(
      "Seeded demo users + permissions (user@gmail.com, user2@gmail.com; permissions can:demo:user1/can:demo:user2)",
    );
  }
}
