const crypto = require("node:crypto");
const fsSync = require("node:fs");
const fs = require("node:fs/promises");
const path = require("node:path");
const postgres = require("postgres");
const { hash } = require("bcryptjs");

function loadEnvFile() {
  const envPath = path.join(process.cwd(), ".env");
  if (!fsSync.existsSync(envPath)) {
    return;
  }

  const raw = fsSync.readFileSync(envPath, "utf8");
  for (const line of raw.split(/\r?\n/)) {
    const trimmed = line.trim();
    if (!trimmed || trimmed.startsWith("#")) continue;

    const separatorIndex = trimmed.indexOf("=");
    if (separatorIndex <= 0) continue;

    const key = trimmed.slice(0, separatorIndex).trim();
    const value = trimmed.slice(separatorIndex + 1).trim();

    if (!(key in process.env)) {
      process.env[key] = value;
    }
  }
}

loadEnvFile();

const DEFAULT_CONNECTION_STRING =
  process.env.DATABASE_URL ||
  "postgres://postgres:postgres@localhost:5432/training_system";

const THAI_YEAR = 2569;
const GREGORIAN_YEAR = THAI_YEAR - 543;
const ORG_SLUG = "uat-2569";
const ORG_NAME = "ชุดข้อมูล UAT 2569";
const OWNER_EMAIL = "uat2569.hrd@training.local";
const EMPLOYEE_PASSWORD = "UAT12345!";
const OWNER_PASSWORD = "UAT12345!";
const EMPLOYEE_EMAIL_DOMAIN = "training.local";
const SEED_PREFIX = "[UAT2569]";
const OWNER_PERMISSIONS = [
  "products:read",
  "products:write",
  "organization:manage",
  "users:manage",
];
const MEMBER_PERMISSIONS = ["products:read"];

const DEPARTMENTS = [
  { code: "IT", name: "IT" },
  { code: "HRD", name: "HRD" },
  { code: "ACC", name: "Accounting" },
  { code: "SAL", name: "Sales" },
  { code: "ENG", name: "Engineering" },
  { code: "OPS", name: "Operations" },
];

const POSITIONS = [
  { code: "MGR", name: "Manager" },
  { code: "SUP", name: "Supervisor" },
  { code: "SNR", name: "Senior Officer" },
  { code: "OFF", name: "Officer" },
  { code: "SPC", name: "Specialist" },
  { code: "ENG", name: "Engineer" },
];

const FIRST_NAMES = [
  "กิตติ",
  "กานต์",
  "เกรียงไกร",
  "ขวัญชัย",
  "คมสัน",
  "จตุพล",
  "จักรพันธ์",
  "ชยพล",
  "ณัฐพงศ์",
  "ณัฐวุฒิ",
  "ดนัย",
  "ธนกร",
  "ธนพล",
  "ธีรภัทร",
  "ธีรวัฒน์",
  "นพดล",
  "นราธิป",
  "นันทชัย",
  "บวร",
  "ประภาส",
  "ปริญญา",
  "ปัญญา",
  "พงศกร",
  "พชร",
  "พีรภัทร",
  "ภาณุ",
  "มนัส",
  "มงคล",
  "ยศกร",
  "รัฐพล",
  "วรพล",
  "วัชรินทร์",
  "วิชญ์",
  "ศรัณย์",
  "ศุภชัย",
  "สกล",
  "สถาพร",
  "สมภพ",
  "สุรชัย",
  "อนุชา",
  "อภิสิทธิ์",
  "อภินันท์",
  "อรรถพล",
  "อัครเดช",
  "อาทิตย์",
  "หญิงนุช",
  "กัลยา",
  "จิราพร",
  "ชุติมา",
  "ณัฐกานต์",
  "ดวงพร",
  "ทิพวรรณ",
  "ธนพร",
  "นภัสสร",
  "บงกช",
  "ปาริฉัตร",
  "พิมพ์ชนก",
  "ภัทรา",
  "มธุรส",
  "รัชนี",
  "วรางคณา",
  "ศศิธร",
  "ศิริพร",
  "สุชาดา",
  "สุพัตรา",
  "อรทัย",
  "อัจฉรา",
];

const LAST_NAMES = [
  "กาญจนศรี",
  "เกษมสุข",
  "แก้วมณี",
  "ขันทอง",
  "คงมั่น",
  "จันทร์เพ็ญ",
  "เจริญกิจ",
  "ชูเกียรติ",
  "ชัยมงคล",
  "ณรงค์เดช",
  "ดวงดี",
  "ทองใบ",
  "ทองสุข",
  "ทิพย์มณี",
  "นาคะเสถียร",
  "บุญมาก",
  "บุญรอด",
  "ประเสริฐศรี",
  "ปรีชานนท์",
  "ผ่องใส",
  "พงษ์พิทักษ์",
  "พิพัฒน์กุล",
  "ภักดี",
  "มณีวงศ์",
  "มหาศาล",
  "มีทรัพย์",
  "รัตนโชติ",
  "รุ่งเรือง",
  "ลือชา",
  "วัฒนากุล",
  "วงศ์สวัสดิ์",
  "ศรีจันทร์",
  "ศรีสุข",
  "สกุลไทย",
  "สถาพรพงศ์",
  "สมบูรณ์ทรัพย์",
  "สวัสดิ์ผล",
  "สิงห์ทอง",
  "สุขเกษม",
  "สุทธิพงศ์",
  "อารยธรรม",
  "อินทรา",
];

const COURSE_TEMPLATES = [
  ["UAT2569-C001", "พื้นฐาน Cybersecurity สำหรับพนักงาน", "K", "IT Academy", 6],
  ["UAT2569-C002", "การใช้งาน Microsoft Excel ขั้นกลาง", "S", "Learning Hub", 6],
  ["UAT2569-C003", "PDPA สำหรับหน่วยงานธุรกิจ", "K", "Compliance Center", 3],
  ["UAT2569-C004", "การสื่อสารอย่างมีประสิทธิภาพ", "S", "People Academy", 4],
  ["UAT2569-C005", "5ส และการจัดระเบียบพื้นที่ทำงาน", "A", "Operations Academy", 3],
  ["UAT2569-C006", "เทคนิคการเจรจาต่อรองการขาย", "S", "Sales Academy", 6],
  ["UAT2569-C007", "พื้นฐาน SQL สำหรับงานวิเคราะห์", "K", "Data Lab", 6],
  ["UAT2569-C008", "ภาวะผู้นำสำหรับหัวหน้างาน", "A", "Leadership Center", 6],
  ["UAT2569-C009", "การควบคุมเอกสาร ISO 9001", "K", "Quality Academy", 4],
  ["UAT2569-C010", "Root Cause Analysis", "S", "Engineering Academy", 6],
  ["UAT2569-C011", "ความปลอดภัยในการทำงานภาคสนาม", "A", "Safety Center", 3],
  ["UAT2569-C012", "การวางแผนกำลังคนและพัฒนาองค์กร", "K", "HRD Academy", 6],
  ["UAT2569-C013", "การวิเคราะห์ต้นทุนการผลิต", "K", "Finance Academy", 6],
  ["UAT2569-C014", "การใช้งาน Power BI เบื้องต้น", "S", "Data Lab", 6],
  ["UAT2569-C015", "Service Mind สำหรับทีมสนับสนุน", "A", "People Academy", 4],
  ["UAT2569-C016", "ITIL Foundation for Support Team", "K", "IT Academy", 8],
  ["UAT2569-C017", "การบริหารคลังสินค้าและรหัสสินค้า", "S", "Operations Academy", 6],
  ["UAT2569-C018", "การอ่านงบการเงินสำหรับผู้จัดการ", "K", "Finance Academy", 4],
  ["UAT2569-C019", "เทคนิคการนำเสนอผลงาน", "S", "Learning Hub", 4],
  ["UAT2569-C020", "การคิดเชิงปรับปรุงอย่างต่อเนื่อง", "A", "Continuous Improvement", 4],
  ["UAT2569-C021", "Secure Coding Awareness", "K", "IT Academy", 6],
  ["UAT2569-C022", "ภาษีมูลค่าเพิ่มสำหรับนักบัญชี", "K", "Finance Academy", 6],
  ["UAT2569-C023", "การใช้ CRM เพื่อเพิ่มยอดขาย", "S", "Sales Academy", 6],
  ["UAT2569-C024", "Lean Manufacturing Basics", "K", "Engineering Academy", 6],
  ["UAT2569-C025", "การให้ Feedback กับทีมงาน", "A", "Leadership Center", 4],
  ["UAT2569-C026", "การบริหารความเสี่ยงองค์กร", "K", "Compliance Center", 4],
  ["UAT2569-C027", "Advanced Excel for Reporting", "S", "Learning Hub", 6],
  ["UAT2569-C028", "พื้นฐานการจัดซื้อจัดจ้าง", "K", "Operations Academy", 4],
  ["UAT2569-C029", "การบริหารข้อร้องเรียนลูกค้า", "A", "Sales Academy", 4],
  ["UAT2569-C030", "PLC Maintenance Essentials", "S", "Engineering Academy", 8],
  ["UAT2569-C031", "People Analytics สำหรับ HRD", "K", "HRD Academy", 6],
  ["UAT2569-C032", "Negotiation for Key Account", "S", "Sales Academy", 8],
  ["UAT2569-C033", "Project Management Fundamentals", "K", "PM Academy", 6],
  ["UAT2569-C034", "Coaching Skills for Supervisor", "A", "Leadership Center", 6],
  ["UAT2569-C035", "ความปลอดภัยข้อมูลและรหัสผ่าน", "A", "IT Academy", 2],
  ["UAT2569-C036", "Kaizen Workshop", "S", "Continuous Improvement", 6],
  ["UAT2569-C037", "Internal Audit Techniques", "K", "Quality Academy", 6],
  ["UAT2569-C038", "Presentation with Data Storytelling", "S", "Data Lab", 6],
  ["UAT2569-C039", "Teamwork and Collaboration", "A", "People Academy", 4],
  ["UAT2569-C040", "Warehouse Safety and Forklift Awareness", "A", "Safety Center", 4],
];

const ANNOUNCEMENT_TEMPLATES = [
  "กำหนดการอบรมความปลอดภัยประจำปี",
  "แจ้งแนวทางยื่นประวัติการอบรมภายในไตรมาส 1",
  "ประชาสัมพันธ์หลักสูตร Leadership รอบเดือนกุมภาพันธ์",
  "เปลี่ยนแปลงเกณฑ์อนุมัติชั่วโมงอบรม",
  "ประกาศวันปิดระบบปรับปรุงฐานข้อมูล",
  "เชิญชวนเข้าร่วมหลักสูตร Power BI สำหรับสายงานสนับสนุน",
  "แจ้งเตือนการอัปโหลดใบรับรองหลังอบรม",
  "กำหนดการทดสอบ Browser UAT รอบฝ่าย HRD",
  "ประกาศผลการทบทวนหลักสูตรบังคับประจำปี",
  "แจ้งปรับปรุงคู่มือใช้งานระบบ TMS",
  "เชิญหัวหน้างานเข้าร่วม Workshop Coaching Skills",
  "ประกาศหลักเกณฑ์การนับชั่วโมง K/S/A",
  "แจ้งเปิดรับข้อเสนอแนะก่อน Sprint 7",
  "ตารางอบรม Mandatory Training รอบ Q3",
  "แจ้งกำหนดการซ้อมแผน BCP และความต่อเนื่องทางธุรกิจ",
];

const IMPORT_TEMPLATE_NAMES = [
  "employee-batch-q1.xlsx",
  "employee-batch-q2.xlsx",
  "employee-batch-q3.xlsx",
];

function createRng(seed) {
  let state = seed >>> 0;
  return () => {
    state = (1664525 * state + 1013904223) >>> 0;
    return state / 4294967296;
  };
}

const random = createRng(2569);

function pick(list) {
  return list[Math.floor(random() * list.length)];
}

function randomInt(min, max) {
  return Math.floor(random() * (max - min + 1)) + min;
}

function chunk(list, size) {
  const chunks = [];
  for (let index = 0; index < list.length; index += size) {
    chunks.push(list.slice(index, index + size));
  }
  return chunks;
}

function makeThaiDate(month, day) {
  return new Date(Date.UTC(GREGORIAN_YEAR, month, day, 9, 0, 0));
}

function formatMonthKey(date) {
  return `${date.getUTCFullYear()}-${String(date.getUTCMonth() + 1).padStart(2, "0")}`;
}

function slugify(value) {
  return value
    .toLowerCase()
    .replace(/[^a-z0-9]+/g, "-")
    .replace(/^-+|-+$/g, "");
}

function toPgTextArray(values) {
  return `{${values
    .map((value) => `"${String(value).replace(/"/g, '\\"')}"`)
    .join(",")}}`;
}

function uniqueNames(count) {
  const result = [];
  const used = new Set();

  for (const firstName of FIRST_NAMES) {
    for (const lastName of LAST_NAMES) {
      const fullName = `${firstName} ${lastName}`;
      if (used.has(fullName)) continue;
      used.add(fullName);
      result.push(fullName);
      if (result.length === count) {
        return result;
      }
    }
  }

  throw new Error("Not enough unique Thai names for seed generation");
}

async function ensureOwner(sql) {
  const passwordHash = await hash(OWNER_PASSWORD, 10);
  const [existing] = await sql`
    select id
    from users
    where email = ${OWNER_EMAIL}
    limit 1
  `;

  if (existing) {
    await sql`
      update users
      set
        name = ${"หัวหน้าฝ่าย HRD UAT"},
        password_hash = ${passwordHash},
        system_role = 'standard',
        is_active = true,
        updated_at = now()
      where id = ${existing.id}
    `;

    return existing.id;
  }

  const userId = crypto.randomUUID();
  await sql`
    insert into users (
      id,
      name,
      email,
      password_hash,
      system_role,
      is_active,
      active_organization_id
    )
    values (
      ${userId},
      ${"หัวหน้าฝ่าย HRD UAT"},
      ${OWNER_EMAIL},
      ${passwordHash},
      'standard',
      true,
      null
    )
  `;

  return userId;
}

async function ensureOrganization(sql, ownerUserId) {
  const [existing] = await sql`
    select id
    from organizations
    where slug = ${ORG_SLUG}
    limit 1
  `;

  if (existing) {
    await sql`
      update users
      set active_organization_id = ${existing.id}, updated_at = now()
      where id = ${ownerUserId}
    `;

    await sql`
      insert into memberships (
        id,
        user_id,
        organization_id,
        role,
        permissions
      )
      values (
        ${crypto.randomUUID()},
        ${ownerUserId},
        ${existing.id},
        'owner',
        ${toPgTextArray(OWNER_PERMISSIONS)}
      )
      on conflict (user_id, organization_id) do update
      set role = 'owner', permissions = ${toPgTextArray(OWNER_PERMISSIONS)}, updated_at = now()
    `;

    return existing.id;
  }

  const organizationId = crypto.randomUUID();
  await sql`
    insert into organizations (
      id,
      name,
      slug,
      plan,
      pending_master_review,
      created_by
    )
    values (
      ${organizationId},
      ${ORG_NAME},
      ${ORG_SLUG},
      'free',
      false,
      ${ownerUserId}
    )
  `;

  await sql`
    update users
    set active_organization_id = ${organizationId}, updated_at = now()
    where id = ${ownerUserId}
  `;

  await sql`
    insert into memberships (
      id,
      user_id,
      organization_id,
      role,
      permissions
    )
    values (
        ${crypto.randomUUID()},
        ${ownerUserId},
        ${organizationId},
        'owner',
        ${toPgTextArray(OWNER_PERMISSIONS)}
      )
      on conflict (user_id, organization_id) do update
      set role = 'owner', permissions = ${toPgTextArray(OWNER_PERMISSIONS)}, updated_at = now()
  `;

  return organizationId;
}

async function cleanupOrganization(sql, organizationId, ownerUserId) {
  const members = await sql`
    select user_id
    from memberships
    where organization_id = ${organizationId}
      and user_id <> ${ownerUserId}
  `;
  const memberIds = members.map((row) => row.user_id);

  await sql`delete from notifications where organization_id = ${organizationId}`;
  await sql`delete from audit_logs where organization_id = ${organizationId}`;
  await sql`delete from announcements where organization_id = ${organizationId}`;
  await sql`delete from certificates where organization_id = ${organizationId}`;
  await sql`delete from training_records where organization_id = ${organizationId}`;
  await sql`delete from import_jobs where organization_id = ${organizationId}`;
  await sql`delete from training_matrices where organization_id = ${organizationId}`;
  await sql`delete from courses where organization_id = ${organizationId}`;
  await sql`delete from training_policies where organization_id = ${organizationId}`;
  await sql`delete from departments where organization_id = ${organizationId}`;
  await sql`delete from positions where organization_id = ${organizationId}`;

  if (memberIds.length > 0) {
    await sql`
      delete from memberships
      where organization_id = ${organizationId}
        and user_id <> ${ownerUserId}
    `;
    await sql`
      delete from users
      where id = any(${sql.array(memberIds, "text")})
    `;
  }
}

async function seedDepartmentsAndPositions(sql, organizationId) {
  for (const department of DEPARTMENTS) {
    await sql`
      insert into departments (
        organization_id,
        code,
        name,
        description,
        is_active,
        pending_master_review
      )
      values (
        ${organizationId},
        ${department.code},
        ${department.name},
        ${`${department.name} department for UAT`}
        ,
        true,
        false
      )
      on conflict (organization_id, name) do update
      set code = excluded.code, description = excluded.description, is_active = true, pending_master_review = false, updated_at = now()
    `;
  }

  for (const position of POSITIONS) {
    await sql`
      insert into positions (
        organization_id,
        code,
        name,
        description,
        is_active,
        pending_master_review
      )
      values (
        ${organizationId},
        ${position.code},
        ${position.name},
        ${`${position.name} role for UAT`}
        ,
        true,
        false
      )
      on conflict (organization_id, name) do update
      set code = excluded.code, description = excluded.description, is_active = true, pending_master_review = false, updated_at = now()
    `;
  }

  const departments = await sql`
    select id, name
    from departments
    where organization_id = ${organizationId}
    order by name asc
  `;
  const positions = await sql`
    select id, name
    from positions
    where organization_id = ${organizationId}
    order by name asc
  `;

  return {
    departments,
    positions,
  };
}

async function seedTrainingPolicy(sql, organizationId) {
  await sql`
    insert into training_policies (
      organization_id,
      year,
      total_hours,
      k_hours,
      s_hours,
      a_hours,
      is_active
    )
    values (
      ${organizationId},
      ${GREGORIAN_YEAR},
      30,
      12,
      12,
      6,
      true
    )
    on conflict (organization_id, year) do update
    set
      total_hours = excluded.total_hours,
      k_hours = excluded.k_hours,
      s_hours = excluded.s_hours,
      a_hours = excluded.a_hours,
      is_active = true,
      updated_at = now()
  `;
}

async function seedEmployees(sql, organizationId, ownerUserId, departmentMap, positionMap) {
  const names = uniqueNames(50);
  const passwordHash = await hash(EMPLOYEE_PASSWORD, 10);
  const distribution = {
    IT: 8,
    HRD: 8,
    Accounting: 8,
    Sales: 8,
    Engineering: 9,
    Operations: 9,
  };
  const positionNames = [
    "Manager",
    "Supervisor",
    "Senior Officer",
    "Officer",
    "Specialist",
    "Engineer",
  ];

  const employees = [];
  let runningIndex = 0;

  for (const [departmentName, count] of Object.entries(distribution)) {
    for (let localIndex = 0; localIndex < count; localIndex += 1) {
      const userId = crypto.randomUUID();
      const employeeNumber = String(runningIndex + 1).padStart(3, "0");
      const email = `uat2569.employee${employeeNumber}@${EMPLOYEE_EMAIL_DOMAIN}`;
      const employeeCode = `UAT2569-${employeeNumber}`;
      const hiredAt = makeThaiDate(
        randomInt(0, 11),
        randomInt(1, 25)
      );
      hiredAt.setUTCFullYear(GREGORIAN_YEAR - randomInt(0, 4));

      const positionName =
        departmentName === "Engineering"
          ? pick(["Engineer", "Senior Officer", "Supervisor"])
          : departmentName === "IT"
            ? pick(["Specialist", "Senior Officer", "Officer"])
            : pick(positionNames);

      await sql`
        insert into users (
          id,
          name,
          email,
          password_hash,
          system_role,
          is_active,
          employee_code,
          department_id,
          position_id,
          company_name,
          hired_at,
          active_organization_id
        )
        values (
          ${userId},
          ${names[runningIndex]},
          ${email},
          ${passwordHash},
          'standard',
          true,
          ${employeeCode},
          ${departmentMap.get(departmentName)},
          ${positionMap.get(positionName)},
          ${ORG_NAME},
          ${hiredAt},
          ${organizationId}
        )
      `;

      await sql`
        insert into memberships (
          id,
          user_id,
          organization_id,
          role,
          permissions
        )
        values (
          ${crypto.randomUUID()},
          ${userId},
          ${organizationId},
          'member',
          ${toPgTextArray(MEMBER_PERMISSIONS)}
        )
      `;

      employees.push({
        id: userId,
        name: names[runningIndex],
        email,
        employeeCode,
        departmentName,
        positionName,
      });

      runningIndex += 1;
    }
  }

  return employees;
}

async function seedCourses(sql, organizationId) {
  const courses = [];

  for (const [courseCode, name, category, organizer, standardHours] of COURSE_TEMPLATES) {
    const [course] = await sql`
      insert into courses (
        organization_id,
        course_code,
        name,
        category,
        organizer,
        standard_hours,
        certificate_required,
        is_required,
        description,
        is_active
      )
      values (
        ${organizationId},
        ${courseCode},
        ${name},
        ${category},
        ${organizer},
        ${standardHours},
        true,
        ${category === "K"},
        ${`${SEED_PREFIX} ${name}`},
        true
      )
      returning id, name, category, organizer, standard_hours
    `;

    courses.push({
      id: course.id,
      name: course.name,
      category,
      organizer: course.organizer,
      standardHours: Number(course.standard_hours),
    });
  }

  return courses;
}

function buildCoursePools(courses) {
  const byName = new Map(courses.map((course) => [course.name, course]));
  return {
    IT: [
      byName.get("พื้นฐาน Cybersecurity สำหรับพนักงาน"),
      byName.get("พื้นฐาน SQL สำหรับงานวิเคราะห์"),
      byName.get("ITIL Foundation for Support Team"),
      byName.get("Secure Coding Awareness"),
      byName.get("ความปลอดภัยข้อมูลและรหัสผ่าน"),
      byName.get("Project Management Fundamentals"),
    ].filter(Boolean),
    HRD: [
      byName.get("การวางแผนกำลังคนและพัฒนาองค์กร"),
      byName.get("People Analytics สำหรับ HRD"),
      byName.get("การสื่อสารอย่างมีประสิทธิภาพ"),
      byName.get("การให้ Feedback กับทีมงาน"),
      byName.get("Coaching Skills for Supervisor"),
      byName.get("Teamwork and Collaboration"),
    ].filter(Boolean),
    Accounting: [
      byName.get("การวิเคราะห์ต้นทุนการผลิต"),
      byName.get("การอ่านงบการเงินสำหรับผู้จัดการ"),
      byName.get("ภาษีมูลค่าเพิ่มสำหรับนักบัญชี"),
      byName.get("PDPA สำหรับหน่วยงานธุรกิจ"),
      byName.get("Advanced Excel for Reporting"),
      byName.get("การบริหารความเสี่ยงองค์กร"),
    ].filter(Boolean),
    Sales: [
      byName.get("เทคนิคการเจรจาต่อรองการขาย"),
      byName.get("การใช้ CRM เพื่อเพิ่มยอดขาย"),
      byName.get("Negotiation for Key Account"),
      byName.get("เทคนิคการนำเสนอผลงาน"),
      byName.get("การบริหารข้อร้องเรียนลูกค้า"),
      byName.get("Presentation with Data Storytelling"),
    ].filter(Boolean),
    Engineering: [
      byName.get("Root Cause Analysis"),
      byName.get("Lean Manufacturing Basics"),
      byName.get("PLC Maintenance Essentials"),
      byName.get("Kaizen Workshop"),
      byName.get("Project Management Fundamentals"),
      byName.get("Internal Audit Techniques"),
    ].filter(Boolean),
    Operations: [
      byName.get("5ส และการจัดระเบียบพื้นที่ทำงาน"),
      byName.get("การบริหารคลังสินค้าและรหัสสินค้า"),
      byName.get("พื้นฐานการจัดซื้อจัดจ้าง"),
      byName.get("ความปลอดภัยในการทำงานภาคสนาม"),
      byName.get("Warehouse Safety and Forklift Awareness"),
      byName.get("การคิดเชิงปรับปรุงอย่างต่อเนื่อง"),
    ].filter(Boolean),
  };
}

async function seedTrainingRecords(sql, organizationId, ownerUserId, employees, courses) {
  const coursePools = buildCoursePools(courses);
  const countsByDepartment = {
    IT: 35,
    HRD: 30,
    Accounting: 35,
    Sales: 45,
    Engineering: 55,
    Operations: 50,
  };
  const approvedMonthlyTargets = [8, 10, 11, 12, 13, 14, 15, 17, 16, 15, 14, 15];
  const approvedCategoryCycle = [
    "K", "S", "K", "A", "S", "K", "S", "K", "A", "K",
  ];
  let approvedMonthIndex = 0;
  let approvedCategoryIndex = 0;
  const trainingRecords = [];
  const statusSummary = { approved: 0, pending: 0, rejected: 0 };
  const departmentSummary = {};
  const monthlyApprovedSummary = {};
  const categorySummary = { K: 0, S: 0, A: 0 };
  const courseSummary = new Map();

  for (const departmentName of Object.keys(countsByDepartment)) {
    const departmentEmployees = employees.filter(
      (employee) => employee.departmentName === departmentName
    );
    const totalRecords = countsByDepartment[departmentName];

    for (let index = 0; index < totalRecords; index += 1) {
      const employee = departmentEmployees[index % departmentEmployees.length];
      const course = pick(coursePools[departmentName]);
      const statusRoll = random();
      const status =
        statusRoll < 0.64 ? "approved" : statusRoll < 0.86 ? "pending" : "rejected";
      let trainingDate;

      if (status === "approved") {
        const monthTarget = approvedMonthlyTargets[approvedMonthIndex % approvedMonthlyTargets.length];
        const month = approvedMonthIndex % 12;
        const positionInMonth =
          Object.values(monthlyApprovedSummary).reduce((sum, value) => sum + value, 0);
        void positionInMonth;
        trainingDate = makeThaiDate(month, randomInt(2, 24));
        approvedMonthlyTargets[approvedMonthIndex % approvedMonthlyTargets.length] = monthTarget - 1;
        if (approvedMonthlyTargets[approvedMonthIndex % approvedMonthlyTargets.length] === 0) {
          approvedMonthIndex += 1;
        }
      } else {
        trainingDate = makeThaiDate(randomInt(0, 11), randomInt(1, 25));
      }

      const hours = Number(
        (course.standardHours + (random() < 0.35 ? 1 : 0)).toFixed(1)
      );
      const category =
        status === "approved"
          ? approvedCategoryCycle[approvedCategoryIndex++ % approvedCategoryCycle.length]
          : null;
      const approvedHours =
        status === "approved"
          ? Number(
              Math.max(hours - (random() < 0.2 ? 1 : 0), 1).toFixed(1)
            )
          : null;
      const reviewedAt =
        status === "pending"
          ? null
          : new Date(trainingDate.getTime() + randomInt(2, 15) * 24 * 60 * 60 * 1000);
      const reviewerNote =
        status === "approved"
          ? pick([
              "ผ่านเกณฑ์และนับชั่วโมงได้ตามหลักสูตร",
              "อนุมัติตามเอกสารและขอบเขตงาน",
              "ผลการอบรมสอดคล้องกับแผนพัฒนารายบุคคล",
            ])
          : null;
      const rejectReason =
        status === "rejected"
          ? pick([
              "เอกสารแนบไม่ครบถ้วน",
              "หัวข้ออบรมไม่ตรงกับนโยบายปี 2569",
              "ชั่วโมงอบรมไม่สามารถยืนยันได้",
            ])
          : null;

      const [record] = await sql`
        insert into training_records (
          organization_id,
          user_id,
          course_id,
          course_name,
          training_date,
          training_type,
          hours,
          approved_hours,
          category,
          organizer,
          note,
          approval_status,
          reviewer_note,
          reject_reason,
          reviewed_by,
          reviewed_at,
          created_by
        )
        values (
          ${organizationId},
          ${employee.id},
          ${course.id},
          ${course.name},
          ${trainingDate},
          ${pick(["online", "onsite", "internal", "external"])},
          ${hours},
          ${approvedHours},
          ${category},
          ${course.organizer},
          ${`${SEED_PREFIX} บันทึกการอบรมของ ${employee.name}`},
          ${status},
          ${reviewerNote},
          ${rejectReason},
          ${status === "pending" ? null : ownerUserId},
          ${reviewedAt},
          ${employee.id}
        )
        returning id, course_name, approval_status, approved_hours, hours, category, user_id, training_date
      `;

      trainingRecords.push({
        id: record.id,
        userId: employee.id,
        courseName: record.course_name,
        status: record.approval_status,
        approvedHours: record.approved_hours === null ? null : Number(record.approved_hours),
        hours: Number(record.hours),
        category: record.category,
        trainingDate: record.training_date,
        departmentName,
      });

      statusSummary[status] += 1;
      departmentSummary[departmentName] = (departmentSummary[departmentName] || 0) + 1;

      if (status === "approved") {
        const monthKey = formatMonthKey(record.training_date);
        monthlyApprovedSummary[monthKey] = (monthlyApprovedSummary[monthKey] || 0) + 1;
        categorySummary[category] += Number(record.approved_hours ?? record.hours);
        courseSummary.set(
          record.course_name,
          (courseSummary.get(record.course_name) || 0) +
            Number(record.approved_hours ?? record.hours)
        );
      }
    }
  }

  return {
    trainingRecords,
    statusSummary,
    departmentSummary,
    monthlyApprovedSummary,
    categorySummary,
    courseSummary: [...courseSummary.entries()]
      .sort((left, right) => right[1] - left[1])
      .slice(0, 5),
  };
}

async function seedAnnouncements(sql, organizationId, ownerUserId) {
  const announcements = [];
  const statusPlan = [
    "published", "published", "published", "published", "published",
    "published", "published", "published", "published", "published",
    "draft", "draft", "draft", "archived", "archived",
  ];

  for (let index = 0; index < ANNOUNCEMENT_TEMPLATES.length; index += 1) {
    const status = statusPlan[index];
    const startDate =
      index < 8
        ? makeThaiDate(Math.max(index - 1, 0), randomInt(1, 10))
        : makeThaiDate(randomInt(0, 11), randomInt(1, 15));
    const endDate =
      status === "archived"
        ? new Date(startDate.getTime() + 40 * 24 * 60 * 60 * 1000)
        : index % 3 === 0
          ? null
          : new Date(startDate.getTime() + randomInt(30, 120) * 24 * 60 * 60 * 1000);

    const [announcement] = await sql`
      insert into announcements (
        organization_id,
        title,
        content,
        start_date,
        end_date,
        is_pin,
        status,
        created_by,
        updated_by
      )
      values (
        ${organizationId},
        ${`${SEED_PREFIX} ${ANNOUNCEMENT_TEMPLATES[index]}`},
        ${`${ANNOUNCEMENT_TEMPLATES[index]} สำหรับการทดสอบระบบ TMS ในปี ${THAI_YEAR} โปรดตรวจสอบรายละเอียดและดำเนินการตามกำหนดเวลา`},
        ${startDate},
        ${endDate},
        ${index < 4},
        ${status},
        ${ownerUserId},
        ${ownerUserId}
      )
      returning id, title, status
    `;

    announcements.push(announcement);
  }

  return announcements;
}

async function seedNotifications(sql, organizationId, ownerUserId, employees, trainingRecords, announcements) {
  const approvedRecords = trainingRecords.filter((record) => record.status === "approved");
  const rejectedRecords = trainingRecords.filter((record) => record.status === "rejected");
  const notifications = [];

  for (const record of approvedRecords.slice(0, 120)) {
    notifications.push({
      organizationId,
      userId: record.userId,
      title: `${SEED_PREFIX} รายการอบรมได้รับการอนุมัติ`,
      message: `${record.courseName} ได้รับอนุมัติชั่วโมงอบรมแล้ว`,
      type: "approved",
      referenceType: "training_record",
      referenceId: String(record.id),
      isRead: random() < 0.55,
    });
  }

  for (const record of rejectedRecords.slice(0, 40)) {
    notifications.push({
      organizationId,
      userId: record.userId,
      title: `${SEED_PREFIX} รายการอบรมไม่ผ่านการอนุมัติ`,
      message: `${record.courseName} ต้องแก้ไขข้อมูลก่อนส่งใหม่`,
      type: "rejected",
      referenceType: "training_record",
      referenceId: String(record.id),
      isRead: random() < 0.45,
    });
  }

  for (const announcement of announcements) {
    const recipients = chunk(
      employees.sort(() => random() - 0.5),
      6
    )[0] || [];
    for (const employee of recipients) {
      notifications.push({
        organizationId,
        userId: employee.id,
        title: `${SEED_PREFIX} ประกาศใหม่`,
        message: `${announcement.title} กรุณาเปิดอ่านรายละเอียด`,
        type: "announcement",
        referenceType: "announcement",
        referenceId: String(announcement.id),
        isRead: random() < 0.35,
      });
    }
  }

  for (let index = 0; index < 20; index += 1) {
    notifications.push({
      organizationId,
      userId: index % 2 === 0 ? ownerUserId : pick(employees).id,
      title: `${SEED_PREFIX} สรุปการนำเข้าพนักงาน`,
      message: `ไฟล์ ${pick(IMPORT_TEMPLATE_NAMES)} ประมวลผลเสร็จสิ้น`,
      type: "reminder",
      referenceType: "import_job",
      referenceId: null,
      isRead: random() < 0.6,
    });
  }

  for (let index = 0; index < 30; index += 1) {
    notifications.push({
      organizationId,
      userId: ownerUserId,
      title: `${SEED_PREFIX} ผลการทบทวนข้อมูลหลัก`,
      message: `รายการข้อมูลหลักรอการติดตามผลลำดับที่ ${index + 1}`,
      type: index % 2 === 0 ? "approved" : "rejected",
      referenceType: "master_review",
      referenceId: String(index + 1),
      isRead: random() < 0.5,
    });
  }

  while (notifications.length < 300) {
    notifications.push({
      organizationId,
      userId: pick(employees).id,
      title: `${SEED_PREFIX} แจ้งเตือนติดตามผลการทดสอบ`,
      message: `กรุณาตรวจสอบข้อมูลการทดสอบรอบที่ ${notifications.length + 1}`,
      type: "reminder",
      referenceType: null,
      referenceId: null,
      isRead: random() < 0.5,
    });
  }

  if (notifications.length > 300) {
    notifications.length = 300;
  }

  for (const notification of notifications) {
    await sql`
      insert into notifications (
        organization_id,
        user_id,
        title,
        message,
        type,
        reference_type,
        reference_id,
        is_read,
        read_at,
        created_at
      )
      values (
        ${notification.organizationId},
        ${notification.userId},
        ${notification.title},
        ${notification.message},
        ${notification.type},
        ${notification.referenceType},
        ${notification.referenceId},
        ${notification.isRead},
        ${notification.isRead ? makeThaiDate(randomInt(0, 11), randomInt(1, 25)) : null},
        ${makeThaiDate(randomInt(0, 11), randomInt(1, 25))}
      )
    `;
  }

  return {
    total: notifications.length,
    unread: notifications.filter((item) => !item.isRead).length,
  };
}

async function seedAuditLogs(sql, organizationId, ownerUserId, employees, trainingSummary, announcements) {
  const auditEntries = [];
  const moduleCatalog = [
    ["TRAINING_RECORDS", "CREATE"],
    ["TRAINING_RECORDS", "UPDATE"],
    ["HRD_REVIEW", "APPROVE"],
    ["HRD_REVIEW", "REJECT"],
    ["ANNOUNCEMENTS", "ANNOUNCEMENT_CREATE"],
    ["ANNOUNCEMENTS", "ANNOUNCEMENT_PUBLISH"],
    ["NOTIFICATIONS", "NOTIFICATION_CREATE"],
    ["EMPLOYEE_IMPORT", "IMPORT"],
    ["MASTER_REVIEW", "APPROVE"],
    ["MASTER_REVIEW", "REJECT"],
  ];
  const topCourses = trainingSummary.courseSummary.map(([name]) => name);

  for (let index = 0; index < 500; index += 1) {
    const [moduleName, action] = moduleCatalog[index % moduleCatalog.length];
    const actor =
      index % 4 === 0 ? ownerUserId : pick(employees).id;
    const entityName =
      moduleName === "ANNOUNCEMENTS"
        ? `${SEED_PREFIX} ${pick(announcements).title}`
        : moduleName === "TRAINING_RECORDS" || moduleName === "HRD_REVIEW"
          ? `${SEED_PREFIX} ${pick(topCourses)}`
          : `${SEED_PREFIX} รายการทดสอบ ${index + 1}`;

    auditEntries.push({
      organizationId,
      userId: actor,
      moduleName,
      action,
      entityName,
      entityId: String(index + 1),
      oldValue:
        action === "UPDATE" || action === "REJECT"
          ? { state: "before", marker: SEED_PREFIX, sequence: index + 1 }
          : null,
      newValue: { state: "after", marker: SEED_PREFIX, sequence: index + 1 },
      ipAddress: `10.25.69.${(index % 200) + 1}`,
      userAgent: "UAT Seed Script",
      createdAt: makeThaiDate(randomInt(0, 11), randomInt(1, 25)),
    });
  }

  for (const entry of auditEntries) {
    await sql`
      insert into audit_logs (
        organization_id,
        user_id,
        module_name,
        action,
        entity_name,
        entity_id,
        old_value,
        new_value,
        ip_address,
        user_agent,
        created_at
      )
      values (
        ${entry.organizationId},
        ${entry.userId},
        ${entry.moduleName},
        ${entry.action},
        ${entry.entityName},
        ${entry.entityId},
        ${entry.oldValue},
        ${entry.newValue},
        ${entry.ipAddress},
        ${entry.userAgent},
        ${entry.createdAt}
      )
    `;
  }

  return auditEntries.length;
}

function buildReviewMarkdown(summary) {
  const departmentRows = Object.entries(summary.departmentDistribution)
    .map(([department, count]) => `- ${department}: ${count} คน`)
    .join("\n");
  const trainingDepartmentRows = Object.entries(summary.trainingRecordDistribution)
    .map(([department, count]) => `- ${department}: ${count} รายการ`)
    .join("\n");
  const statusRows = Object.entries(summary.trainingStatusDistribution)
    .map(([status, count]) => `- ${status}: ${count} รายการ`)
    .join("\n");
  const monthRows = Object.entries(summary.monthlyApprovedDistribution)
    .sort(([left], [right]) => left.localeCompare(right))
    .map(([month, count]) => `- ${month}: ${count} รายการอนุมัติ`)
    .join("\n");
  const categoryRows = Object.entries(summary.categoryHoursDistribution)
    .map(([category, hours]) => `- ${category}: ${hours.toFixed(1)} ชั่วโมง`)
    .join("\n");
  const courseRows = summary.topCourses
    .map(([courseName, hours]) => `- ${courseName}: ${hours.toFixed(1)} ชั่วโมง`)
    .join("\n");

  return `# UAT Seed Data Review

## 1. Records generated

- Organization: ${summary.organizationName} (\`${summary.organizationSlug}\`)
- HRD owner account: \`${summary.ownerEmail}\`
- Employee password: \`${summary.defaultPassword}\`
- Employees: ${summary.counts.employees}
- Courses: ${summary.counts.courses}
- Training Records: ${summary.counts.trainingRecords}
- Announcements: ${summary.counts.announcements}
- Notifications: ${summary.counts.notifications}
- Audit Logs: ${summary.counts.auditLogs}
- Training Policy Year: Thai ${THAI_YEAR} (stored as Gregorian ${GREGORIAN_YEAR})

## 2. Distribution summary

### Employee distribution by department

${departmentRows}

### Training record distribution by department

${trainingDepartmentRows}

### Training status distribution

${statusRows}

### Approved K/S/A hours distribution

${categoryRows}

### Monthly approved trend

${monthRows}

## 3. Dashboard validation

The generated dataset was shaped to support meaningful dashboard output:

- Department Ranking:
  - multiple departments have approved-hour activity
  - Engineering and Operations contain higher approved-hour volume
- Course Ranking:
  - repeated course usage creates stable top-course results
  - expected top courses:
${courseRows}
- K/S/A Distribution:
  - approved records include mixed K/S/A category coverage
- Monthly Trend:
  - approved records are spread across all 12 months of Thai year ${THAI_YEAR}

## 4. Report validation

The seed set supports report verification for:

- employee-level training history
- HRD organization-wide pending review visibility
- approved vs pending vs rejected comparison
- yearly filtering for Thai year ${THAI_YEAR}
- course-level and department-level aggregation
- notification center pagination and unread counts
- audit log filtering by module and action

## 5. Known limitations

- Thai year ${THAI_YEAR} is stored in the database as Gregorian year ${GREGORIAN_YEAR} because application queries use Gregorian year boundaries
- Seeded notifications and announcements include the ${SEED_PREFIX} marker for easy identification during UAT
- No certificate files are generated in this seed set
- This script seeds a dedicated UAT organization and does not try to merge into existing business organizations
`;
}

async function writeReview(summary) {
  const target = path.join(process.cwd(), "docs", "uat-seed-data-review.md");
  await fs.writeFile(target, buildReviewMarkdown(summary), "utf8");
}

async function main() {
  if (!DEFAULT_CONNECTION_STRING) {
    throw new Error("DATABASE_URL is required");
  }

  const sql = postgres(DEFAULT_CONNECTION_STRING, { prepare: false });

  try {
    const ownerUserId = await ensureOwner(sql);
    const organizationId = await ensureOrganization(sql, ownerUserId);
    await cleanupOrganization(sql, organizationId, ownerUserId);
    const { departments, positions } = await seedDepartmentsAndPositions(sql, organizationId);
    await seedTrainingPolicy(sql, organizationId);

    const departmentMap = new Map(departments.map((row) => [row.name, row.id]));
    const positionMap = new Map(positions.map((row) => [row.name, row.id]));

    const employees = await seedEmployees(
      sql,
      organizationId,
      ownerUserId,
      departmentMap,
      positionMap
    );
    const courses = await seedCourses(sql, organizationId);
    const trainingSummary = await seedTrainingRecords(
      sql,
      organizationId,
      ownerUserId,
      employees,
      courses
    );
    const announcements = await seedAnnouncements(sql, organizationId, ownerUserId);
    const notificationSummary = await seedNotifications(
      sql,
      organizationId,
      ownerUserId,
      employees,
      trainingSummary.trainingRecords,
      announcements
    );
    const auditLogCount = await seedAuditLogs(
      sql,
      organizationId,
      ownerUserId,
      employees,
      trainingSummary,
      announcements
    );

    const summary = {
      organizationName: ORG_NAME,
      organizationSlug: ORG_SLUG,
      ownerEmail: OWNER_EMAIL,
      defaultPassword: EMPLOYEE_PASSWORD,
      counts: {
        employees: employees.length,
        courses: courses.length,
        trainingRecords: trainingSummary.trainingRecords.length,
        announcements: announcements.length,
        notifications: notificationSummary.total,
        auditLogs: auditLogCount,
      },
      departmentDistribution: employees.reduce((accumulator, employee) => {
        accumulator[employee.departmentName] =
          (accumulator[employee.departmentName] || 0) + 1;
        return accumulator;
      }, {}),
      trainingRecordDistribution: trainingSummary.departmentSummary,
      trainingStatusDistribution: trainingSummary.statusSummary,
      monthlyApprovedDistribution: trainingSummary.monthlyApprovedSummary,
      categoryHoursDistribution: trainingSummary.categorySummary,
      topCourses: trainingSummary.courseSummary,
    };

    await writeReview(summary);

    console.log("UAT seed data created successfully.");
    console.log(JSON.stringify(summary, null, 2));
  } finally {
    await sql.end({ timeout: 1 });
  }
}

main().catch((error) => {
  console.error("Failed to seed UAT data.", error);
  process.exitCode = 1;
});
