import bcrypt from 'bcryptjs';
import { getDefaultPermissions, getDefaultRoleProfiles } from '../../lib/auth/rbac.ts';
import { createSqlClient, deterministicId, type SqlClient } from '../../../scripts/lib/seed-utils.ts';

type OrganizationRow = {
  id: string;
  name: string;
  createdBy: string;
};

type OptionRow = {
  id: string;
  category: string;
  code: string;
};

type RoleProfileRow = {
  id: string;
  code: string;
};

type UatRoleAssignmentSeed = {
  roleCode: string;
  isPrimary: boolean;
  branchScopeMode: 'all' | 'selected' | 'inherit';
  branchCodes?: string[];
  productTypeScopeMode: 'all' | 'selected' | 'inherit';
  productTypeCodes?: string[];
};

type UatUserSeed = {
  key: string;
  name: string;
  email: string;
  password: string;
  systemRole: 'super_admin' | 'user';
  membershipRole: 'admin' | 'user';
  businessRole:
    | 'marketing'
    | 'sales'
    | 'sales_support'
    | 'sales_manager'
    | 'department_manager'
    | 'top_manager'
    | 'crm_admin';
  roleAssignments: UatRoleAssignmentSeed[];
};

const UAT_PASSWORD = 'UatDemo123!';

const UAT_USERS: UatUserSeed[] = [
  {
    key: 'mk',
    name: 'MK UAT',
    email: 'mk.uat@alla.local',
    password: UAT_PASSWORD,
    systemRole: 'user',
    membershipRole: 'user',
    businessRole: 'marketing',
    roleAssignments: [
      {
        roleCode: 'marketing',
        isPrimary: true,
        branchScopeMode: 'all',
        productTypeScopeMode: 'all'
      }
    ]
  },
  {
    key: 'sales-crane',
    name: 'Sales Crane UAT',
    email: 'sales.crane.uat@alla.local',
    password: UAT_PASSWORD,
    systemRole: 'user',
    membershipRole: 'user',
    businessRole: 'sales',
    roleAssignments: [
      {
        roleCode: 'sales',
        isPrimary: true,
        branchScopeMode: 'all',
        productTypeScopeMode: 'selected',
        productTypeCodes: ['crane']
      }
    ]
  },
  {
    key: 'sales-dockdoor',
    name: 'Sales Dock Door UAT',
    email: 'sales.dockdoor.uat@alla.local',
    password: UAT_PASSWORD,
    systemRole: 'user',
    membershipRole: 'user',
    businessRole: 'sales',
    roleAssignments: [
      {
        roleCode: 'sales',
        isPrimary: true,
        branchScopeMode: 'all',
        productTypeScopeMode: 'selected',
        productTypeCodes: ['dockdoor']
      }
    ]
  },
  {
    key: 'mgr-sales',
    name: 'Sales Manager UAT',
    email: 'mgr.sales.uat@alla.local',
    password: UAT_PASSWORD,
    systemRole: 'user',
    membershipRole: 'user',
    businessRole: 'sales_manager',
    roleAssignments: [
      {
        roleCode: 'sales_manager',
        isPrimary: true,
        branchScopeMode: 'all',
        productTypeScopeMode: 'all'
      },
      {
        roleCode: 'department_manager',
        isPrimary: false,
        branchScopeMode: 'all',
        productTypeScopeMode: 'all'
      }
    ]
  },
  {
    key: 'ceo',
    name: 'CEO UAT',
    email: 'ceo.uat@alla.local',
    password: UAT_PASSWORD,
    systemRole: 'user',
    membershipRole: 'user',
    businessRole: 'top_manager',
    roleAssignments: [
      {
        roleCode: 'top_manager',
        isPrimary: true,
        branchScopeMode: 'all',
        productTypeScopeMode: 'all'
      }
    ]
  },
  {
    key: 'admin',
    name: 'Admin UAT',
    email: 'admin.uat@alla.local',
    password: UAT_PASSWORD,
    systemRole: 'user',
    membershipRole: 'admin',
    businessRole: 'crm_admin',
    roleAssignments: [
      {
        roleCode: 'crm_admin',
        isPrimary: true,
        branchScopeMode: 'all',
        productTypeScopeMode: 'all'
      }
    ]
  }
];

async function ensureRoleProfiles(
  sql: SqlClient,
  organization: OrganizationRow
): Promise<Map<string, RoleProfileRow>> {
  const defaultProfiles = getDefaultRoleProfiles();

  for (const profile of defaultProfiles) {
    await sql`
      insert into crm_role_profiles (
        id,
        organization_id,
        code,
        name,
        description,
        permissions,
        branch_scope_mode,
        product_scope_mode,
        ownership_scope,
        approval_authority,
        is_system,
        is_active,
        created_by,
        updated_by
      ) values (
        ${deterministicId(`crm-role-profile:${organization.id}:${profile.code}`)},
        ${organization.id},
        ${profile.code},
        ${profile.name},
        ${profile.description},
        ${sql.array(profile.permissions)},
        ${profile.branchScopeMode},
        ${profile.productScopeMode},
        ${profile.ownershipScope},
        ${profile.approvalAuthority},
        ${profile.isSystem},
        ${true},
        ${organization.createdBy},
        ${organization.createdBy}
      )
      on conflict (organization_id, code) do update
      set
        name = excluded.name,
        description = excluded.description,
        permissions = excluded.permissions,
        branch_scope_mode = excluded.branch_scope_mode,
        product_scope_mode = excluded.product_scope_mode,
        ownership_scope = excluded.ownership_scope,
        approval_authority = excluded.approval_authority,
        is_system = excluded.is_system,
        is_active = excluded.is_active,
        updated_by = excluded.updated_by,
        deleted_at = null,
        updated_at = now()
    `;
  }

  const rows = await sql<RoleProfileRow[]>`
    select id, code
    from crm_role_profiles
    where organization_id = ${organization.id}
      and deleted_at is null
  `;

  return new Map(rows.map((row) => [row.code, row]));
}

async function loadOptions(sql: SqlClient, organizationId: string): Promise<Map<string, OptionRow>> {
  const rows = await sql<OptionRow[]>`
    select id, category, code
    from ms_options
    where organization_id = ${organizationId}
      and deleted_at is null
      and is_active = true
  `;

  return new Map(rows.map((row) => [`${row.category}:${row.code}`, row]));
}

function resolveScopeIds(
  optionMap: Map<string, OptionRow>,
  category: string,
  codes: string[] | undefined
): string[] {
  if (!codes?.length) {
    return [];
  }

  return codes.map((code) => {
    const row = optionMap.get(`${category}:${code}`);

    if (!row) {
      throw new Error(`Missing ${category} option for code ${code}`);
    }

    return row.id;
  });
}

async function upsertUatUser(
  sql: SqlClient,
  organization: OrganizationRow,
  optionMap: Map<string, OptionRow>,
  roleProfileMap: Map<string, RoleProfileRow>,
  seed: UatUserSeed
) {
  const passwordHash = await bcrypt.hash(seed.password, 10);
  const [userRow] = await sql<{ id: string }[]>`
    insert into users (
      id,
      name,
      email,
      password_hash,
      system_role,
      active_organization_id
    ) values (
      ${deterministicId(`uat-user:${seed.email}`)},
      ${seed.name},
      ${seed.email},
      ${passwordHash},
      ${seed.systemRole},
      ${organization.id}
    )
    on conflict (email) do update
    set
      name = excluded.name,
      password_hash = excluded.password_hash,
      system_role = excluded.system_role,
      active_organization_id = excluded.active_organization_id,
      updated_at = now()
    returning id
  `;

  const membershipPermissions = getDefaultPermissions(seed.membershipRole, seed.businessRole);
  const existingMembership =
    (
      await sql<{ id: string }[]>`
        select id
        from memberships
        where user_id = ${userRow.id}
          and organization_id = ${organization.id}
        limit 1
      `
    )[0] ?? null;

  if (existingMembership) {
    await sql`
      update memberships
      set
        role = ${seed.membershipRole},
        business_role = ${seed.businessRole},
        permissions = ${sql.array(membershipPermissions)},
        branch_scope_ids = ${sql.array([])},
        product_type_scope_ids = ${sql.array([])},
        updated_at = now()
      where id = ${existingMembership.id}
    `;
  } else {
    await sql`
      insert into memberships (
        id,
        user_id,
        organization_id,
        role,
        business_role,
        permissions,
        branch_scope_ids,
        product_type_scope_ids
      ) values (
        ${deterministicId(`membership:${organization.id}:${seed.email}`)},
        ${userRow.id},
        ${organization.id},
        ${seed.membershipRole},
        ${seed.businessRole},
        ${sql.array(membershipPermissions)},
        ${sql.array([])},
        ${sql.array([])}
      )
    `;
  }

  await sql`
    delete from crm_user_role_assignments
    where organization_id = ${organization.id}
      and user_id = ${userRow.id}
  `;

  for (const assignment of seed.roleAssignments) {
    const roleProfile = roleProfileMap.get(assignment.roleCode);

    if (!roleProfile) {
      throw new Error(`Missing CRM role profile ${assignment.roleCode}`);
    }

    await sql`
      insert into crm_user_role_assignments (
        id,
        organization_id,
        user_id,
        role_profile_id,
        branch_scope_mode,
        branch_scope_ids,
        product_type_scope_mode,
        product_type_scope_ids,
        is_primary,
        is_active,
        assigned_by,
        assigned_at,
        created_at,
        updated_at,
        deleted_at
      ) values (
        ${deterministicId(`crm-role-assignment:${organization.id}:${seed.email}:${assignment.roleCode}`)},
        ${organization.id},
        ${userRow.id},
        ${roleProfile.id},
        ${assignment.branchScopeMode},
        ${sql.array(resolveScopeIds(optionMap, 'crm_branch', assignment.branchCodes))},
        ${assignment.productTypeScopeMode},
        ${sql.array(resolveScopeIds(optionMap, 'crm_product_type', assignment.productTypeCodes))},
        ${assignment.isPrimary},
        ${true},
        ${organization.createdBy},
        ${new Date()},
        ${new Date()},
        ${new Date()},
        ${null}
      )
    `;
  }
}

async function seedOrganization(sql: SqlClient, organization: OrganizationRow) {
  const optionMap = await loadOptions(sql, organization.id);
  const roleProfileMap = await ensureRoleProfiles(sql, organization);

  for (const seed of UAT_USERS) {
    await upsertUatUser(sql, organization, optionMap, roleProfileMap, seed);
  }

  console.log(`[seed:uat-users] ${organization.name}: ${UAT_USERS.length} UAT users ready`);
}

async function main() {
  const sql = createSqlClient();

  try {
    const organizations = await sql<OrganizationRow[]>`
      select id, name, created_by as "createdBy"
      from organizations
      order by name asc
    `;

    if (organizations.length === 0) {
      throw new Error('No organizations found. Run seed:system first.');
    }

    for (const organization of organizations) {
      await seedOrganization(sql, organization);
    }
  } finally {
    await sql.end();
  }
}

main().catch((error) => {
  console.error('[seed:uat-users] Failed:', error instanceof Error ? error.message : String(error));
  process.exitCode = 1;
});
