import {
  PrismaClient,
  RiskStatus,
  TodoStatus,
  Role,
  PriorityStatus,
  SecurityType,
} from "@prisma/client";
import { PrismaPg } from "@prisma/adapter-pg";
import pg from "pg";
import bcrypt from "bcryptjs";

import {
  firstNames,
  lastNames,
  jobTitles,
  actionVerbs,
  subjects,
  loremWords,
  phoneNumbers,
  middleNames,
  departments,
} from "./mockData";

const sentenceFrom = (seed: number, min = 10, max = 22) => {
  const len = min + (seed % (max - min + 1));
  const words: string[] = [];
  let i = seed;
  for (let c = 0; c < len; c++) {
    i = (i * 9301 + 49297) % 233280;
    words.push(loremWords[i % loremWords.length]);
  }
  const s = words.join(" ");
  return s.charAt(0).toUpperCase() + s.slice(1) + ".";
};

const uniqueTitle = (idx: number) => {
  const verb = actionVerbs[idx % actionVerbs.length];
  const subj = subjects[(idx * 7) % subjects.length];
  return `${verb} ${subj} #${idx + 1}`;
};

const uniqueTask = (idx: number) => {
  // Blend a seeded lorem sentence with a hint to ensure uniqueness
  const hint = `${firstNames[idx % firstNames.length]} ${
    lastNames[(idx * 3) % lastNames.length]
  } ${jobTitles[(idx * 5) % jobTitles.length]}`;
  return `${sentenceFrom(idx)}\n\nContext: ${hint} [T${idx + 1}]`;
};

const pool = new pg.Pool({
  connectionString: process.env.DATABASE_URL,
});
const adapter = new PrismaPg(pool, { schema: "Athena" });
const prisma = new PrismaClient({ adapter });

const getRandom = <T>(arr: T[]): T =>
  arr[Math.floor(Math.random() * arr.length)];

// Determine security type by role for consistent seed data
const securityFor = (role: Role): SecurityType => {
  switch (role) {
    case Role.MANAGER:
      return SecurityType.MAXIMUM;
    case Role.ADMIN:
      return SecurityType.ASSISTANCE;
    case Role.USER:
    default:
      return SecurityType.BASIC;
  }
};

async function main() {
  console.log("🌱 Starting seed...");
  await prisma.todo.deleteMany();
  await prisma.user.deleteMany();

  const users = [] as Array<Awaited<ReturnType<typeof prisma.user.create>>>;

  const defaultPassword = await bcrypt.hash("password", 10);

  const superAdmin = await prisma.user.create({
    data: {
      firstName: "Super",
      middleName: "John Travolta",
      lastName: "Admin",
      email: "superadmin@example.com",
      password: defaultPassword,
      riskStatus: RiskStatus.NO_RISK,
      phoneNumber: "1234567890",
      department: "Admin",
      imageUrl: null,
      title: "Administrator",
      isLocked: false,
      isActivated: true,
      role: Role.MANAGER,
      securityType: securityFor(Role.MANAGER),
    },
  });
  users.push(superAdmin);

  // Second MANAGER (Manager)
  const managerSuperAdmin = await prisma.user.create({
    data: {
      firstName: "Manager",
      middleName: "John Travolta",
      lastName: "SuperAdmin",
      email: "manager@example.com",
      password: defaultPassword,
      riskStatus: RiskStatus.NO_RISK,
      phoneNumber: "1234567890",
      department: "Admin",
      imageUrl: null,
      title: "Administrator",
      isLocked: false,
      isActivated: true,
      role: Role.MANAGER,
      securityType: securityFor(Role.MANAGER),
    },
  });
  users.push(managerSuperAdmin);

  // Create 10 ADMIN users and split them between the two MANAGERs' teams
  const teamAAdmins: Array<Awaited<ReturnType<typeof prisma.user.create>>> = [];
  const teamBAdmins: Array<Awaited<ReturnType<typeof prisma.user.create>>> = [];
  for (let i = 1; i <= 10; i++) {
    const assignedManagerId = i <= 5 ? superAdmin.id : managerSuperAdmin.id;
    const adminUser = await prisma.user.create({
      data: {
        firstName: `Admin${i}`,
        middleName: "Admin",
        lastName: "User",
        email: `admin${i}@example.com`,
        phoneNumber: `123456789${i}`,
        department: "Admin",
        password: defaultPassword,
        riskStatus: RiskStatus.NO_RISK,
        imageUrl: null,
        title: "Administrator",
        isLocked: false,
        isActivated: true,
        role: Role.ADMIN,
        securityType: securityFor(Role.ADMIN),
        managerId: assignedManagerId,
      },
    });
    users.push(adminUser);
    if (assignedManagerId === superAdmin.id) {
      teamAAdmins.push(adminUser);
    } else {
      teamBAdmins.push(adminUser);
    }
  }

  for (let i = 0; i < 100; i++) {
    const firstName = getRandom(firstNames);
    const lastName = getRandom(lastNames);
    const middleName = getRandom(middleNames);
    const phoneNumber = getRandom(phoneNumbers);
    const department = getRandom(departments);

    const user = await prisma.user.create({
      data: {
        firstName,
        middleName,
        lastName,
        email: `${firstName.toLowerCase()}.${lastName.toLowerCase()}${i}@example.com`,
        phoneNumber,
        department,
        password: defaultPassword,
        riskStatus: getRandom(Object.values(RiskStatus)),
        imageUrl:
          Math.random() > 0.5
            ? `https://i.pravatar.cc/150?u=${firstName}${lastName}${i}`
            : null,
        title: getRandom(jobTitles),
        isLocked: false,
        isActivated: true,
        role: Role.USER,
        securityType: securityFor(Role.USER),
      },
    });

    users.push(user);
  }

  // Build team pools (Manager + their Admins)
  const teamA = [superAdmin, ...teamAAdmins];
  const teamB = [managerSuperAdmin, ...teamBAdmins];

  // Helper: get a random subset from an array (unique, size k)
  const pickSubset = <T extends { id: string }>(arr: T[], k: number): T[] => {
    const n = Math.max(1, Math.min(k, arr.length));
    const pool = [...arr];
    for (let i = pool.length - 1; i > 0; i--) {
      const j = Math.floor(Math.random() * (i + 1));
      [pool[i], pool[j]] = [pool[j], pool[i]];
    }
    return pool.slice(0, n);
  };

  // Create 50 todos split evenly between the two teams.
  const totalTodos = 50;
  const half = Math.floor(totalTodos / 2);
  let todoIndex = 0;

  const createTeamTodo = async (
    team: Array<Awaited<ReturnType<typeof prisma.user.create>>>,
  ) => {
    const createdByUser = getRandom(team);
    // 1-3 assignees from the same team (ensure createdBy is included)
    const baseAssignees = pickSubset(
      team,
      1 + Math.floor(Math.random() * Math.min(3, team.length)),
    );
    const ensureCreatedBy = baseAssignees.some((u) => u.id === createdByUser.id)
      ? baseAssignees
      : [...baseAssignees, createdByUser];
    const uniqueAssignees = Array.from(
      new Map(ensureCreatedBy.map((u) => [u.id, u])).values(),
    );

    const dueDate = new Date();
    dueDate.setDate(dueDate.getDate() + Math.floor(Math.random() * 30));

    await prisma.todo.create({
      data: {
        title: uniqueTitle(todoIndex),
        task: uniqueTask(todoIndex),
        status: getRandom(Object.values(TodoStatus)),
        priority: getRandom(Object.values(PriorityStatus)),
        dueDate,
        createdBy: { connect: { id: createdByUser.id } },
        assignees: { connect: uniqueAssignees.map((u) => ({ id: u.id })) },
      },
    });
    todoIndex += 1;
  };

  for (let i = 0; i < half; i++) {
    await createTeamTodo(teamA);
  }
  for (let i = 0; i < totalTodos - half; i++) {
    await createTeamTodo(teamB);
  }

  console.log(`✅ Seeded ${users.length} users and 50 todos`);
}

main()
  .catch((e) => {
    console.error("❌ Seed error:", e);
    process.exit(1);
  })
  .finally(async () => {
    await prisma.$disconnect();
  });
