import { useMutation, useQuery } from "@tanstack/react-query";
import type { AxiosError } from "axios";
import {
  ArrowDown,
  ArrowUp,
  ArrowUpDown,
  Building2,
  ChevronDown,
  ChevronRight,
  Download,
  ExternalLink,
  FileSpreadsheet,
  LayoutDashboard,
  List,
  Network,
  Plus,
  RefreshCw,
  Search,
  Trash2,
  Upload,
} from "lucide-react";
import * as React from "react";
import { Link, useNavigate } from "react-router-dom";
import {
  sortItems,
  toggleSort,
  type SortConfig,
  type SortResolverMap,
} from "../../../../../common/core/utils";
import { RoleGuard } from "../../../components/auth/RoleGuard";
import { Badge } from "../../../components/ui/badge";
import { Button } from "../../../components/ui/button";
import {
  Card,
  CardContent,
  CardDescription,
  CardHeader,
  CardTitle,
} from "../../../components/ui/card";
import { Checkbox } from "../../../components/ui/checkbox";
import {
  Dialog,
  DialogContent,
  DialogDescription,
  DialogFooter,
  DialogHeader,
  DialogTitle,
} from "../../../components/ui/dialog";
import { Input } from "../../../components/ui/input";
import { ScrollArea } from "../../../components/ui/scroll-area";
import { Separator } from "../../../components/ui/separator";
import {
  Table,
  TableBody,
  TableCell,
  TableHead,
  TableHeader,
  TableRow,
} from "../../../components/ui/table";
import {
  Tabs,
  TabsContent,
  TabsList,
  TabsTrigger,
} from "../../../components/ui/tabs";
import {
  type TenantSummary,
  deleteTenant,
  deleteTenantsBulk,
  exportTenantsCSV,
  fetchMe,
  fetchTenants,
  importTenantsCSV,
} from "../../../lib/adminApi";
import { t } from "../../../lib/i18n";
import { type TenantNode, buildTenantFullTree } from "../../../lib/tenantTree";
import { isSeedTenant } from "../utils/protectedTenants";
import {
  type TenantImportPreviewRow,
  type TenantImportResolution,
  buildTenantImportParentOptionGroups,
  buildTenantImportPreview,
  inferTenantImportRootParentSlug,
  parseTenantCSV,
  serializeTenantImportCSV,
} from "../utils/tenantCsvImport";

const tenantCSVTemplate =
  "name,type,parent_tenant_slug,slug,memo,email_domain\n";

type TenantSortKey = keyof TenantSummary | "recursiveMemberCount";

const getTenantIcon = (type?: string) => {
  switch (type?.toUpperCase()) {
    case "COMPANY_GROUP":
      return Network;
    case "ORGANIZATION":
    case "USER_GROUP":
      return Network;
    default:
      return Building2;
  }
};

const noImportParentRef = "__none__";

function tenantParentRef(tenantId: string) {
  return `tenant:${tenantId}`;
}

function previewParentRef(rowNumber: number) {
  return `row:${rowNumber}`;
}

function slugParentRef(slug: string) {
  return `slug:${slug}`;
}

function getImportParentGroupLabel(type: string) {
  switch (type) {
    case "COMPANY_GROUP":
      return t(
        "ui.admin.tenants.import_preview.parent_company_groups",
        "기존 Company Group",
      );
    case "COMPANY":
      return t(
        "ui.admin.tenants.import_preview.parent_companies",
        "기존 Company",
      );
    case "ORGANIZATION":
      return t(
        "ui.admin.tenants.import_preview.parent_organizations",
        "기존 Organization",
      );
    default:
      return type;
  }
}

function resolveDefaultImportParentRef(
  preview: TenantImportPreviewRow,
  previewRows: TenantImportPreviewRow[],
  tenants: TenantSummary[],
) {
  if (preview.row.parentTenantId) {
    const parentPreview = previewRows.find(
      (candidate) =>
        candidate.row.rowNumber !== preview.row.rowNumber &&
        candidate.row.tenantId === preview.row.parentTenantId,
    );
    if (parentPreview) {
      return previewParentRef(parentPreview.row.rowNumber);
    }
    return tenantParentRef(preview.row.parentTenantId);
  }
  if (!preview.row.parentTenantSlug) {
    return noImportParentRef;
  }

  const normalizedSlug = preview.row.parentTenantSlug.toLowerCase();
  const existingTenant = tenants.find(
    (tenant) => tenant.slug.toLowerCase() === normalizedSlug,
  );
  if (existingTenant) {
    return tenantParentRef(existingTenant.id);
  }

  const parentPreview = previewRows.find(
    (candidate) =>
      candidate.row.rowNumber !== preview.row.rowNumber &&
      candidate.row.slug.toLowerCase() === normalizedSlug,
  );
  if (parentPreview) {
    return previewParentRef(parentPreview.row.rowNumber);
  }

  return slugParentRef(preview.row.parentTenantSlug);
}

function selectedImportSlug(
  preview: TenantImportPreviewRow,
  selectedCreateSlugs: Record<number, string>,
) {
  return (
    selectedCreateSlugs[preview.row.rowNumber] || preview.defaultCreateSlug
  );
}

function resolveImportParentSelection(
  parentRef: string,
  previewRows: TenantImportPreviewRow[],
  selectedMatches: Record<number, string>,
  selectedCreateSlugs: Record<number, string>,
) {
  if (!parentRef || parentRef === noImportParentRef) {
    return { parentTenantId: "", parentTenantSlug: "" };
  }
  if (parentRef.startsWith("tenant:")) {
    return {
      parentTenantId: parentRef.slice("tenant:".length),
      parentTenantSlug: "",
    };
  }
  if (parentRef.startsWith("slug:")) {
    return { parentTenantSlug: parentRef.slice("slug:".length) };
  }
  if (parentRef.startsWith("row:")) {
    const rowNumber = Number(parentRef.slice("row:".length));
    const selected = selectedMatches[rowNumber] ?? "__create__";
    if (selected && selected !== "__create__") {
      return { parentTenantId: selected, parentTenantSlug: "" };
    }
    const parentPreview = previewRows.find(
      (preview) => preview.row.rowNumber === rowNumber,
    );
    return {
      parentTenantSlug: parentPreview
        ? selectedImportSlug(parentPreview, selectedCreateSlugs)
        : "",
    };
  }
  return {};
}

function TenantListPage() {
  const navigate = useNavigate();
  const [viewMode, setViewMode] = React.useState<"list" | "hierarchy">("list");
  const [selectedIds, setSelectedIds] = React.useState<string[]>([]);
  const [search, setSearch] = React.useState("");
  const [sortConfig, setSortConfig] =
    React.useState<SortConfig<TenantSortKey> | null>(null);
  const fileInputRef = React.useRef<HTMLInputElement | null>(null);
  const [importMessage, setImportMessage] = React.useState("");
  const [previewRows, setPreviewRows] = React.useState<
    TenantImportPreviewRow[]
  >([]);
  const [selectedMatches, setSelectedMatches] = React.useState<
    Record<number, string>
  >({});
  const [selectedCreateSlugs, setSelectedCreateSlugs] = React.useState<
    Record<number, string>
  >({});
  const [selectedParentRefs, setSelectedParentRefs] = React.useState<
    Record<number, string>
  >({});
  const [previewOpen, setPreviewOpen] = React.useState(false);

  const { data: profile } = useQuery({
    queryKey: ["me"],
    queryFn: fetchMe,
  });

  // Redirect tenant_admin ONLY if they have one or fewer manageable tenants in the list
  React.useEffect(() => {
    if (profile?.role === "tenant_admin") {
      const manageableCount = profile.manageableTenants?.length ?? 0;
      if (
        (manageableCount === 1 || manageableCount === 0) &&
        profile.tenantId
      ) {
        navigate(`/tenants/${profile.tenantId}`, { replace: true });
      }
    }
  }, [profile, navigate]);

  const query = useQuery({
    queryKey: ["tenants", { limit: 1000, offset: 0 }],
    queryFn: () => fetchTenants(1000, 0),
    enabled:
      profile?.role === "super_admin" ||
      (profile?.role === "tenant_admin" &&
        (profile.manageableTenants?.length ?? 0) > 1),
  });

  const deleteMutation = useMutation({
    mutationFn: (tenantId: string) => deleteTenant(tenantId),
    onSuccess: () => {
      query.refetch();
    },
  });

  const deleteBulkMutation = useMutation({
    mutationFn: (ids: string[]) => deleteTenantsBulk(ids),
    onSuccess: () => {
      setSelectedIds([]);
      query.refetch();
    },
  });

  const exportMutation = useMutation({
    mutationFn: (includeIds: boolean) => exportTenantsCSV(includeIds),
    onSuccess: ({ blob, filename }) => {
      const url = window.URL.createObjectURL(blob);
      const link = document.createElement("a");
      link.href = url;
      link.download = filename;
      document.body.appendChild(link);
      link.click();
      link.remove();
      window.URL.revokeObjectURL(url);
    },
  });

  const importMutation = useMutation({
    mutationFn: (file: File) => importTenantsCSV(file),
    onSuccess: (result) => {
      setImportMessage(
        t(
          "msg.admin.tenants.import_result",
          "생성 {{created}}, 갱신 {{updated}}, 실패 {{failed}}",
          {
            created: result.created,
            updated: result.updated,
            failed: result.failed,
          },
        ),
      );
      setPreviewOpen(false);
      setPreviewRows([]);
      setSelectedMatches({});
      setSelectedParentRefs({});
      query.refetch();
    },
    onError: (error: AxiosError<{ error?: string }>) => {
      setImportMessage(
        error.response?.data?.error ??
          t(
            "msg.admin.tenants.import_error",
            "테넌트 가져오기에 실패했습니다.",
          ),
      );
    },
  });

  if (
    profile &&
    profile.role !== "super_admin" &&
    profile.role !== "tenant_admin"
  ) {
    return (
      <div className="flex h-[50vh] flex-col items-center justify-center space-y-4">
        <h3 className="text-xl font-bold">
          {t("msg.admin.common.forbidden", "접근 권한이 없습니다.")}
        </h3>
        <Button onClick={() => navigate("/")}>
          {t("ui.common.go_home", "홈으로 이동")}
        </Button>
      </div>
    );
  }

  if (
    profile?.role === "tenant_admin" &&
    (profile.manageableTenants?.length ?? 0) <= 1
  ) {
    return null;
  }

  const errorMsg = (query.error as AxiosError<{ error?: string }>)?.response
    ?.data?.error;
  const fallbackError =
    !errorMsg && query.isError
      ? t("msg.admin.tenants.fetch_error", "테넌트 목록 조회에 실패했습니다.")
      : null;

  const allTenants = query.data?.items ?? [];
  const importParentOptionGroups =
    buildTenantImportParentOptionGroups(allTenants);
  const tenantSortResolvers = React.useMemo<
    SortResolverMap<TenantSummary & { recursiveMemberCount: number }, TenantSortKey>
  >(
    () => ({
      recursiveMemberCount: (tenant) => tenant.recursiveMemberCount,
    }),
    [],
  );
  const tenants = React.useMemo(() => {
    // 1. Calculate recursive counts
    // buildTenantFullTree returns subTree which represents roots, but it also mutates the mapped nodes internally.
    // However, to easily map them back to a flat list, we can just run the builder,
    // and then extract the recursive counts.
    const treeResult = buildTenantFullTree(allTenants);

    // Flatten the tree or just extract from allTenants map?
    // buildTenantFullTree does NOT mutate the objects passed in allTenants. It creates new ones.
    // Let's create a map of id -> recursiveMemberCount
    const recursiveCounts = new Map<string, number>();
    const extractCounts = (nodes: TenantNode[]) => {
      for (const node of nodes) {
        recursiveCounts.set(node.id, node.recursiveMemberCount);
        if (node.children) extractCounts(node.children);
      }
    };
    extractCounts(treeResult.subTree);

    let enriched = allTenants.map((t) => ({
      ...t,
      recursiveMemberCount: recursiveCounts.get(t.id) ?? t.memberCount ?? 0,
    }));

    if (search.trim()) {
      const term = search.toLowerCase();
      enriched = enriched.filter(
        (t) =>
          t.name.toLowerCase().includes(term) ||
          t.slug.toLowerCase().includes(term),
      );
    }

    return sortItems(enriched, sortConfig, tenantSortResolvers);
  }, [allTenants, search, sortConfig, tenantSortResolvers]);

  const requestSort = (key: TenantSortKey) => {
    setSortConfig((current) => toggleSort(current, key));
  };

  const getSortIcon = (key: TenantSortKey) => {
    if (!sortConfig || sortConfig.key !== key) {
      return <ArrowUpDown size={14} className="ml-1 opacity-50" />;
    }
    return sortConfig.direction === "asc" ? (
      <ArrowUp size={14} className="ml-1" />
    ) : (
      <ArrowDown size={14} className="ml-1" />
    );
  };

  const deletableTenants = React.useMemo(
    () => tenants.filter((tenant) => !isSeedTenant(tenant)),
    [tenants],
  );

  const handleSelectAll = (checked: boolean) => {
    if (checked) {
      setSelectedIds(deletableTenants.map((t) => t.id));
    } else {
      setSelectedIds([]);
    }
  };

  const handleSelect = (tenant: TenantSummary, checked: boolean) => {
    if (isSeedTenant(tenant)) {
      return;
    }
    if (checked) {
      setSelectedIds((prev) => [...prev, tenant.id]);
    } else {
      setSelectedIds((prev) => prev.filter((i) => i !== tenant.id));
    }
  };

  const handleDeleteBulk = () => {
    if (selectedIds.length === 0) return;
    const deletableIds = selectedIds.filter((id) =>
      deletableTenants.some((tenant) => tenant.id === id),
    );
    if (deletableIds.length === 0) return;
    if (
      !window.confirm(
        t(
          "msg.admin.tenants.delete_bulk_confirm",
          "선택한 {{count}}개 테넌트를 삭제할까요?",
          { count: deletableIds.length },
        ),
      )
    ) {
      return;
    }
    deleteBulkMutation.mutate(deletableIds);
  };

  const handleTemplateDownload = () => {
    const blob = new Blob([tenantCSVTemplate], { type: "text/csv" });
    const url = window.URL.createObjectURL(blob);
    const link = document.createElement("a");
    link.href = url;
    link.download = "tenant-import-template.csv";
    document.body.appendChild(link);
    link.click();
    link.remove();
    window.URL.revokeObjectURL(url);
  };

  const handleImportFile = async (
    event: React.ChangeEvent<HTMLInputElement>,
  ) => {
    const file = event.target.files?.[0];
    event.target.value = "";
    if (!file) return;
    setImportMessage("");
    const text = await file.text();
    const rows = parseTenantCSV(text, {
      rootParentSlug: inferTenantImportRootParentSlug(file.name, allTenants),
    });
    if (rows.length === 0) {
      setImportMessage(
        t("msg.admin.tenants.import_empty", "가져올 테넌트 행이 없습니다."),
      );
      return;
    }
    const preview = buildTenantImportPreview(rows, allTenants);
    setPreviewRows(preview);
    setSelectedMatches(
      Object.fromEntries(
        preview.map((row) => [
          row.row.rowNumber,
          row.defaultTenantId || "__create__",
        ]),
      ),
    );
    setSelectedCreateSlugs(
      Object.fromEntries(
        preview.map((row) => [row.row.rowNumber, row.defaultCreateSlug]),
      ),
    );
    setSelectedParentRefs(
      Object.fromEntries(
        preview.map((row) => [
          row.row.rowNumber,
          resolveDefaultImportParentRef(row, preview, allTenants),
        ]),
      ),
    );
    setPreviewOpen(true);
  };

  const handleImportConfirm = () => {
    const resolutions: Record<number, TenantImportResolution> =
      Object.fromEntries(
        previewRows.map((preview) => {
          const selected = selectedMatches[preview.row.rowNumber] ?? "";
          if (selected && selected !== "__create__") {
            return [
              preview.row.rowNumber,
              {
                mode: "existing",
                tenantId: selected,
                ...resolveImportParentSelection(
                  selectedParentRefs[preview.row.rowNumber] ??
                    resolveDefaultImportParentRef(
                      preview,
                      previewRows,
                      allTenants,
                    ),
                  previewRows,
                  selectedMatches,
                  selectedCreateSlugs,
                ),
              },
            ];
          }

          return [
            preview.row.rowNumber,
            {
              mode: "create",
              slug:
                selectedCreateSlugs[preview.row.rowNumber] ||
                preview.defaultCreateSlug,
              ...resolveImportParentSelection(
                selectedParentRefs[preview.row.rowNumber] ??
                  resolveDefaultImportParentRef(
                    preview,
                    previewRows,
                    allTenants,
                  ),
                previewRows,
                selectedMatches,
                selectedCreateSlugs,
              ),
            },
          ];
        }),
      );
    const csv = serializeTenantImportCSV(previewRows, resolutions);
    const file = new File([csv], "tenants.csv", { type: "text/csv" });
    importMutation.mutate(file);
  };

  const handleDelete = (tenantId: string, tenantName: string) => {
    const tenant = allTenants.find((item) => item.id === tenantId);
    if (tenant && isSeedTenant(tenant)) {
      return;
    }
    if (
      !window.confirm(
        t(
          "msg.admin.tenants.delete_confirm",
          '테넌트 "{{name}}"를 삭제할까요?',
          { name: tenantName },
        ),
      )
    ) {
      return;
    }
    deleteMutation.mutate(tenantId);
  };

  return (
    <div className="space-y-6 flex flex-col h-[calc(100vh-theme(spacing.32))]">
      <header className="flex flex-wrap items-start justify-between gap-4 flex-shrink-0 sticky top-[-2.5rem] z-20 bg-background/95 backdrop-blur pt-4 pb-2 -mt-4">
        <div className="space-y-2">
          <h2 className="text-3xl font-semibold">
            {t("ui.admin.tenants.title", "테넌트 목록")}
          </h2>
          <p className="text-sm text-[var(--color-muted)]">
            {t(
              "msg.admin.tenants.subtitle",
              "시스템에 등록된 모든 테넌트를 평면 목록으로 확인하고 관리합니다.",
            )}
          </p>
        </div>
        <div className="flex items-center gap-2">
          <div className="relative w-64 mr-2">
            <Search className="absolute left-2.5 top-2.5 h-4 w-4 text-muted-foreground" />
            <Input
              placeholder={t(
                "ui.admin.tenants.list.search_placeholder",
                "테넌트 이름 또는 슬러그 검색...",
              )}
              className="pl-9 h-9"
              value={search}
              onChange={(e) => setSearch(e.target.value)}
            />
          </div>

          <RoleGuard roles={["super_admin"]}>
            {selectedIds.length > 0 && (
              <Button
                variant="destructive"
                onClick={handleDeleteBulk}
                disabled={deleteBulkMutation.isPending}
                className="gap-2"
              >
                <Trash2 size={16} />
                {t("ui.admin.tenants.delete_selected", "선택 삭제")} (
                {selectedIds.length})
              </Button>
            )}
          </RoleGuard>

          <RoleGuard roles={["super_admin"]}>
            <input
              ref={fileInputRef}
              type="file"
              accept=".csv,text/csv"
              className="hidden"
              data-testid="tenant-import-input"
              onChange={handleImportFile}
            />
            <Button
              variant="outline"
              onClick={handleTemplateDownload}
              data-testid="tenant-template-btn"
            >
              <FileSpreadsheet size={16} />
              {t("ui.admin.tenants.csv_template", "템플릿")}
            </Button>
            <Button
              variant="outline"
              onClick={() => exportMutation.mutate(false)}
              disabled={exportMutation.isPending}
              data-testid="tenant-export-btn"
            >
              <Download size={16} />
              {t("ui.admin.tenants.export_without_ids", "UUID 제외 내보내기")}
            </Button>
            <Button
              variant="outline"
              onClick={() => exportMutation.mutate(true)}
              disabled={exportMutation.isPending}
              data-testid="tenant-export-with-ids-btn"
            >
              <Download size={16} />
              {t("ui.admin.tenants.export_with_ids", "UUID 포함")}
            </Button>
            <Button
              variant="outline"
              onClick={() => fileInputRef.current?.click()}
              disabled={importMutation.isPending}
              data-testid="tenant-import-btn"
            >
              <Upload size={16} />
              {t("ui.admin.tenants.import", "가져오기")}
            </Button>
          </RoleGuard>

          <Button
            variant="outline"
            onClick={() => query.refetch()}
            disabled={query.isFetching}
          >
            <RefreshCw size={16} />
            {t("ui.common.refresh", "새로고침")}
          </Button>
          <RoleGuard roles={["super_admin"]}>
            <Button asChild>
              <Link to="/tenants/new">
                <Plus size={16} />
                {t("ui.admin.tenants.add", "테넌트 추가")}
              </Link>
            </Button>
          </RoleGuard>
        </div>
        {importMessage && (
          <div
            className="basis-full rounded-md border border-border bg-secondary px-3 py-2 text-sm"
            data-testid="tenant-import-result"
          >
            {importMessage}
          </div>
        )}
      </header>

      <Card className="bg-[var(--color-panel)] flex-1 flex flex-col min-h-0 overflow-hidden">
        <CardHeader className="flex flex-row items-center justify-between flex-shrink-0">
          <div className="flex items-center gap-6">
            <div>
              <CardTitle>
                {t("ui.admin.tenants.registry.title", "Tenant Registry")}
              </CardTitle>
              <CardDescription>
                {t(
                  "msg.admin.tenants.registry.count",
                  "총 {{count}}개 테넌트",
                  {
                    count: query.data?.total ?? 0,
                  },
                )}
              </CardDescription>
            </div>

            <Tabs
              value={viewMode}
              onValueChange={(v) => setViewMode(v as "list" | "hierarchy")}
              className="w-[280px]"
            >
              <TabsList className="grid w-full grid-cols-2 p-1 bg-muted/60 border border-border/50">
                <TabsTrigger
                  value="list"
                  className="text-xs font-semibold data-[state=active]:bg-primary data-[state=active]:text-primary-foreground transition-all"
                >
                  <List className="w-3.5 h-3.5 mr-1.5" />
                  {t("ui.admin.tenants.view.list", "평면 목록")}
                </TabsTrigger>
                <TabsTrigger
                  value="hierarchy"
                  className="text-xs font-semibold data-[state=active]:bg-primary data-[state=active]:text-primary-foreground transition-all"
                >
                  <Network className="w-3.5 h-3.5 mr-1.5" />
                  {t("ui.admin.tenants.view.hierarchy", "계층 구조")}
                </TabsTrigger>
              </TabsList>
            </Tabs>
          </div>
        </CardHeader>
        <CardContent className="flex-1 flex flex-col min-h-0 pt-0">
          {(errorMsg || fallbackError) && (
            <div className="mb-4 rounded-lg border border-destructive/40 bg-destructive/10 px-3 py-2 text-sm text-destructive flex-shrink-0">
              {errorMsg ?? fallbackError}
            </div>
          )}

          <Tabs value={viewMode} className="flex-1 flex flex-col min-h-0">
            <TabsContent
              value="list"
              className="flex-1 flex flex-col min-h-0 m-0"
            >
              <div className="flex-1 rounded-md border overflow-hidden flex flex-col">
                <div className="flex-1 overflow-auto relative custom-scrollbar">
                  <Table className="min-w-[1180px]">
                    <TableHeader className="sticky top-0 z-10 bg-secondary shadow-sm">
                      <TableRow>
                        <TableHead className="w-[48px] whitespace-nowrap">
                          <Checkbox
                            checked={
                              tenants.length > 0 &&
                              deletableTenants.length > 0 &&
                              selectedIds.length === deletableTenants.length
                            }
                            onCheckedChange={(checked) =>
                              handleSelectAll(!!checked)
                            }
                          />
                        </TableHead>
                        <TableHead
                          className="min-w-[220px] cursor-pointer hover:bg-muted/50 transition-colors whitespace-nowrap"
                          onClick={() => requestSort("id")}
                        >
                          <div className="flex items-center">
                            {t("ui.admin.tenants.table.id", "ID")}
                            {getSortIcon("id")}
                          </div>
                        </TableHead>
                        <TableHead
                          className="cursor-pointer hover:bg-muted/50 transition-colors whitespace-nowrap"
                          onClick={() => requestSort("name")}
                        >
                          <div className="flex items-center">
                            {t("ui.admin.tenants.table.name", "NAME")}
                            {getSortIcon("name")}
                          </div>
                        </TableHead>
                        <TableHead
                          className="cursor-pointer hover:bg-muted/50 transition-colors whitespace-nowrap"
                          onClick={() => requestSort("type")}
                        >
                          <div className="flex items-center">
                            {t("ui.admin.tenants.table.type", "TYPE")}
                            {getSortIcon("type")}
                          </div>
                        </TableHead>
                        <TableHead
                          className="cursor-pointer hover:bg-muted/50 transition-colors whitespace-nowrap"
                          onClick={() => requestSort("slug")}
                        >
                          <div className="flex items-center">
                            {t("ui.admin.tenants.table.slug", "SLUG")}
                            {getSortIcon("slug")}
                          </div>
                        </TableHead>
                        <TableHead
                          className="cursor-pointer hover:bg-muted/50 transition-colors whitespace-nowrap"
                          onClick={() => requestSort("status")}
                        >
                          <div className="flex items-center">
                            {t("ui.admin.tenants.table.status", "STATUS")}
                            {getSortIcon("status")}
                          </div>
                        </TableHead>
                        <TableHead
                          className="cursor-pointer hover:bg-muted/50 transition-colors whitespace-nowrap"
                          onClick={() => requestSort("recursiveMemberCount")}
                        >
                          <div className="flex items-center">
                            {t("ui.admin.tenants.table.members", "MEMBERS")}
                            {getSortIcon("recursiveMemberCount")}
                          </div>
                        </TableHead>
                        <TableHead
                          className="cursor-pointer hover:bg-muted/50 transition-colors whitespace-nowrap"
                          onClick={() => requestSort("updatedAt")}
                        >
                          <div className="flex items-center">
                            {t("ui.admin.tenants.table.updated", "UPDATED")}
                            {getSortIcon("updatedAt")}
                          </div>
                        </TableHead>
                        <TableHead className="whitespace-nowrap">
                          {t("ui.common.actions", "액션")}
                        </TableHead>
                      </TableRow>
                    </TableHeader>
                    <TableBody>
                      {query.isLoading && (
                        <TableRow>
                          <TableCell colSpan={9}>
                            {t("msg.common.loading", "로딩 중...")}
                          </TableCell>
                        </TableRow>
                      )}
                      {!query.isLoading && tenants.length === 0 && (
                        <TableRow>
                          <TableCell
                            colSpan={9}
                            className="text-center py-8 text-muted-foreground"
                          >
                            {t(
                              "msg.admin.tenants.empty",
                              "아직 등록된 테넌트가 없습니다.",
                            )}
                          </TableCell>
                        </TableRow>
                      )}
                      {tenants.map((tenant) => (
                        <TableRow key={tenant.id}>
                          <TableCell className="text-center">
                            {isSeedTenant(tenant) ? (
                              <span className="inline-block h-4 w-4" />
                            ) : (
                              <Checkbox
                                checked={selectedIds.includes(tenant.id)}
                                onCheckedChange={(checked) =>
                                  handleSelect(tenant, !!checked)
                                }
                              />
                            )}
                          </TableCell>
                          <TableCell
                            className="max-w-[260px] break-all font-mono text-xs text-muted-foreground"
                            data-testid={`tenant-internal-id-${tenant.id}`}
                          >
                            {tenant.id}
                          </TableCell>
                          <TableCell className="font-semibold">
                            <div className="flex flex-wrap items-center gap-2">
                              <Link
                                to={`/tenants/${tenant.id}`}
                                className="hover:underline text-primary cursor-pointer"
                              >
                                {tenant.name}
                              </Link>
                              {isSeedTenant(tenant) && (
                                <Badge
                                  variant="secondary"
                                  className="text-[10px]"
                                >
                                  {t(
                                    "ui.admin.tenants.seed_badge",
                                    "초기 설정",
                                  )}
                                </Badge>
                              )}
                            </div>
                          </TableCell>
                          <TableCell className="whitespace-nowrap">
                            <Badge
                              variant="outline"
                              className="text-[10px] font-mono"
                            >
                              {tenant.type}
                            </Badge>
                          </TableCell>
                          <TableCell className="font-mono text-xs">
                            {tenant.slug}
                          </TableCell>
                          <TableCell className="whitespace-nowrap">
                            <Badge
                              variant={
                                tenant.status === "active"
                                  ? "default"
                                  : tenant.status === "pending"
                                    ? "secondary"
                                    : "muted"
                              }
                            >
                              {t(
                                `ui.common.status.${tenant.status}`,
                                tenant.status,
                              )}
                            </Badge>
                          </TableCell>
                          <TableCell className="font-medium">
                            {tenant.recursiveMemberCount}
                          </TableCell>
                          <TableCell className="whitespace-nowrap text-xs">
                            {tenant.updatedAt
                              ? new Date(tenant.updatedAt).toLocaleString(
                                  "ko-KR",
                                )
                              : "-"}
                          </TableCell>
                          <TableCell>
                            <Button
                              variant="ghost"
                              size="sm"
                              disabled={
                                isSeedTenant(tenant) || deleteMutation.isPending
                              }
                              onClick={() =>
                                handleDelete(tenant.id, tenant.name)
                              }
                              className="text-destructive hover:text-destructive hover:bg-destructive/10"
                            >
                              <Trash2 size={14} className="mr-2" />
                              {t("ui.common.delete", "삭제")}
                            </Button>
                          </TableCell>
                        </TableRow>
                      ))}
                    </TableBody>
                  </Table>
                </div>
              </div>
            </TabsContent>

            <TabsContent
              value="hierarchy"
              className="flex-1 flex flex-col min-h-0 m-0 overflow-hidden"
            >
              <TenantHierarchyView tenants={allTenants} />
            </TabsContent>
          </Tabs>
        </CardContent>
      </Card>

      <Dialog open={previewOpen} onOpenChange={setPreviewOpen}>
        <DialogContent className="max-w-5xl">
          <DialogHeader>
            <DialogTitle>
              {t("ui.admin.tenants.import_preview.title", "CSV 가져오기 확인")}
            </DialogTitle>
            <DialogDescription>
              {t(
                "msg.admin.tenants.import_preview.description",
                "tenant_id가 없는 행은 기존 테넌트 후보와 비교한 뒤 신규 생성 또는 기존 테넌트 갱신으로 처리합니다.",
              )}
            </DialogDescription>
          </DialogHeader>

          <div className="max-h-[60vh] overflow-auto rounded-md border">
            <Table>
              <TableHeader className="sticky top-0 bg-secondary">
                <TableRow>
                  <TableHead className="w-[72px]">
                    {t("ui.common.row", "행")}
                  </TableHead>
                  <TableHead>
                    {t("ui.admin.tenants.table.name", "NAME")}
                  </TableHead>
                  <TableHead>
                    {t("ui.admin.tenants.table.slug", "SLUG")}
                  </TableHead>
                  <TableHead>
                    {t("ui.admin.tenants.import_preview.parent", "상위")}
                  </TableHead>
                  <TableHead>
                    {t("ui.admin.tenants.import_preview.match", "매칭")}
                  </TableHead>
                  <TableHead>
                    {t("ui.admin.tenants.import_preview.candidates", "후보")}
                  </TableHead>
                </TableRow>
              </TableHeader>
              <TableBody>
                {previewRows.map((preview) => (
                  <TableRow
                    key={preview.row.rowNumber}
                    data-testid={`tenant-import-preview-row-${preview.row.rowNumber}`}
                  >
                    <TableCell className="font-mono text-xs">
                      {preview.row.rowNumber}
                    </TableCell>
                    <TableCell className="font-medium">
                      {preview.row.name}
                    </TableCell>
                    <TableCell className="font-mono text-xs">
                      {preview.row.slug}
                      {preview.conflicts.length > 0 && (
                        <div className="mt-1 flex flex-wrap gap-1">
                          {preview.conflicts.map((conflict) => (
                            <Badge
                              key={conflict}
                              variant="outline"
                              className="text-[10px]"
                            >
                              {conflict === "external_tenant_id"
                                ? t(
                                    "ui.admin.tenants.import_preview.external_id",
                                    "외부 ID",
                                  )
                                : conflict === "slug_exists"
                                  ? t(
                                      "ui.admin.tenants.import_preview.slug_exists",
                                      "slug 충돌",
                                    )
                                  : t(
                                      "ui.admin.tenants.import_preview.parent_unresolved",
                                      "부모 확인 필요",
                                    )}
                            </Badge>
                          ))}
                        </div>
                      )}
                    </TableCell>
                    <TableCell>
                      <select
                        className="h-9 w-full min-w-[220px] rounded-md border border-input bg-background px-3 text-sm"
                        value={
                          selectedParentRefs[preview.row.rowNumber] ??
                          resolveDefaultImportParentRef(
                            preview,
                            previewRows,
                            allTenants,
                          )
                        }
                        data-testid={`tenant-import-parent-select-${preview.row.rowNumber}`}
                        onChange={(event) =>
                          setSelectedParentRefs((prev) => ({
                            ...prev,
                            [preview.row.rowNumber]: event.target.value,
                          }))
                        }
                      >
                        <option value={noImportParentRef}>
                          {t("ui.common.none", "없음")}
                        </option>
                        {importParentOptionGroups.map((group) => (
                          <optgroup
                            key={group.type}
                            label={getImportParentGroupLabel(group.type)}
                          >
                            {group.tenants.map((tenant) => (
                              <option
                                key={tenant.id}
                                value={tenantParentRef(tenant.id)}
                              >
                                {tenant.name} ({tenant.slug}) - {tenant.type}
                              </option>
                            ))}
                          </optgroup>
                        ))}
                        <optgroup
                          label={t(
                            "ui.admin.tenants.import_preview.csv_parents",
                            "가져오기 CSV",
                          )}
                        >
                          {previewRows
                            .filter(
                              (candidate) =>
                                candidate.row.rowNumber !==
                                preview.row.rowNumber,
                            )
                            .map((candidate) => (
                              <option
                                key={candidate.row.rowNumber}
                                value={previewParentRef(
                                  candidate.row.rowNumber,
                                )}
                              >
                                {candidate.row.name} (
                                {selectedImportSlug(
                                  candidate,
                                  selectedCreateSlugs,
                                )}
                                )
                              </option>
                            ))}
                        </optgroup>
                        {(
                          selectedParentRefs[preview.row.rowNumber] ??
                          resolveDefaultImportParentRef(
                            preview,
                            previewRows,
                            allTenants,
                          )
                        ).startsWith("slug:") && (
                          <option
                            value={
                              selectedParentRefs[preview.row.rowNumber] ??
                              resolveDefaultImportParentRef(
                                preview,
                                previewRows,
                                allTenants,
                              )
                            }
                          >
                            {preview.row.parentTenantSlug}
                          </option>
                        )}
                      </select>
                    </TableCell>
                    <TableCell>
                      <div className="space-y-2">
                        <select
                          className="h-9 w-full rounded-md border border-input bg-background px-3 text-sm"
                          value={
                            selectedMatches[preview.row.rowNumber] ??
                            "__create__"
                          }
                          data-testid={`tenant-import-match-select-${preview.row.rowNumber}`}
                          onChange={(event) =>
                            setSelectedMatches((prev) => ({
                              ...prev,
                              [preview.row.rowNumber]: event.target.value,
                            }))
                          }
                        >
                          <option value="__create__">
                            {t(
                              "ui.admin.tenants.import_preview.create_new_reset",
                              "신규 생성 (ID/slug 재설정)",
                            )}
                          </option>
                          {preview.candidates.map((candidate) => (
                            <option
                              key={candidate.tenantId}
                              value={candidate.tenantId}
                            >
                              {candidate.name} ({candidate.slug})
                            </option>
                          ))}
                        </select>
                        {(selectedMatches[preview.row.rowNumber] ??
                          "__create__") === "__create__" && (
                          <Input
                            value={
                              selectedCreateSlugs[preview.row.rowNumber] ?? ""
                            }
                            data-testid={`tenant-import-create-slug-${preview.row.rowNumber}`}
                            onChange={(event) =>
                              setSelectedCreateSlugs((prev) => ({
                                ...prev,
                                [preview.row.rowNumber]: event.target.value,
                              }))
                            }
                          />
                        )}
                      </div>
                    </TableCell>
                    <TableCell>
                      {preview.candidates.length > 0 ? (
                        <div className="flex flex-wrap gap-1">
                          {preview.candidates.map((candidate) => (
                            <Badge
                              key={candidate.tenantId}
                              variant={
                                candidate.score >= 0.95 ? "default" : "outline"
                              }
                              data-testid="tenant-import-candidate"
                            >
                              {candidate.name}{" "}
                              {Math.round(candidate.score * 100)}%
                            </Badge>
                          ))}
                        </div>
                      ) : (
                        <span className="text-sm text-muted-foreground">
                          {t(
                            "ui.admin.tenants.import_preview.no_candidates",
                            "후보 없음",
                          )}
                        </span>
                      )}
                    </TableCell>
                  </TableRow>
                ))}
              </TableBody>
            </Table>
          </div>

          <DialogFooter>
            <Button variant="outline" onClick={() => setPreviewOpen(false)}>
              {t("ui.common.cancel", "취소")}
            </Button>
            <Button
              onClick={handleImportConfirm}
              disabled={importMutation.isPending}
              data-testid="tenant-import-confirm-btn"
            >
              {t("ui.admin.tenants.import_preview.confirm", "가져오기 실행")}
            </Button>
          </DialogFooter>
        </DialogContent>
      </Dialog>
    </div>
  );
}

// --- Internal Support Components ---

const HierarchyNode: React.FC<{
  node: TenantNode;
  level: number;
  selectedId: string | null;
  onSelect: (id: string) => void;
  isExpandedInitial?: boolean;
}> = ({ node, level, selectedId, onSelect, isExpandedInitial = false }) => {
  const [isExpanded, setIsExpanded] = React.useState(isExpandedInitial);
  const isSelected = selectedId === node.id;
  const hasChildren = node.children && node.children.length > 0;
  const TypeIcon = getTenantIcon(node.type);

  return (
    <div className="flex flex-col">
      <button
        type="button"
        onClick={() => onSelect(node.id)}
        className={`flex items-center justify-between w-full px-2 py-1.5 rounded-md text-left transition-colors ${
          isSelected
            ? "bg-primary text-primary-foreground shadow-sm"
            : "hover:bg-muted"
        }`}
      >
        <div className="flex items-center min-w-0">
          <div className="w-5 flex items-center justify-center mr-1">
            {hasChildren ? (
              <span
                onClick={(e) => {
                  e.stopPropagation();
                  setIsExpanded(!isExpanded);
                }}
                onKeyUp={(e) => {
                  if (e.key === "Enter" || e.key === " ") {
                    e.stopPropagation();
                    setIsExpanded(!isExpanded);
                  }
                }}
                className="p-0.5 hover:bg-black/5 rounded cursor-pointer"
              >
                {isExpanded ? (
                  <ChevronDown size={14} />
                ) : (
                  <ChevronRight size={14} />
                )}
              </span>
            ) : (
              level > 0 && <div className="w-1 h-1 rounded-full bg-border" />
            )}
          </div>
          <TypeIcon
            size={14}
            className={`mr-2 shrink-0 ${isSelected ? "text-primary-foreground" : "text-muted-foreground"}`}
          />
          <span className="text-xs truncate font-medium">{node.name}</span>
        </div>
        <Badge
          variant={isSelected ? "secondary" : "outline"}
          className="ml-2 text-[9px] px-1 h-3.5 min-w-[1.2rem] justify-center"
        >
          {node.recursiveMemberCount}
        </Badge>
      </button>

      {isExpanded && hasChildren && (
        <div className="flex flex-col ml-3 pl-1 border-l border-border/50">
          {node.children.map((child) => (
            <HierarchyNode
              key={child.id}
              node={child}
              level={level + 1}
              selectedId={selectedId}
              onSelect={onSelect}
            />
          ))}
        </div>
      )}
    </div>
  );
};

const TenantHierarchyView: React.FC<{
  tenants: TenantSummary[];
}> = ({ tenants }) => {
  const navigate = useNavigate();
  const { subTree } = React.useMemo(
    () => buildTenantFullTree(tenants),
    [tenants],
  );
  const [selectedId, setSelectedId] = React.useState<string | null>(
    subTree[0]?.id || null,
  );

  const tenantMap = React.useMemo(() => {
    const m = new Map<string, TenantNode>();
    const fill = (nodes: TenantNode[]) => {
      for (const n of nodes) {
        m.set(n.id, n);
        if (n.children) fill(n.children);
      }
    };
    fill(subTree);
    return m;
  }, [subTree]);

  const selectedNode = selectedId ? tenantMap.get(selectedId) : null;

  const siblings = React.useMemo(() => {
    if (!selectedNode) return [];
    if (!selectedNode.parentId) return subTree;
    const parent = tenantMap.get(selectedNode.parentId);
    return parent?.children ?? [];
  }, [selectedNode, subTree, tenantMap]);

  return (
    <div className="flex-1 flex gap-4 overflow-hidden min-h-0 mt-4">
      {/* Sidebar: Tree */}
      <Card className="w-72 flex flex-col bg-muted/20 border shadow-none">
        <CardHeader className="p-3 border-b bg-muted/10">
          <CardTitle className="text-[10px] uppercase tracking-wider text-muted-foreground font-bold">
            조직 계층도
          </CardTitle>
        </CardHeader>
        <CardContent className="p-2 overflow-hidden flex-1">
          <ScrollArea className="h-full">
            <div className="space-y-0.5">
              {subTree.map((node) => (
                <HierarchyNode
                  key={node.id}
                  node={node}
                  level={0}
                  selectedId={selectedId}
                  onSelect={setSelectedId}
                  isExpandedInitial={true}
                />
              ))}
            </div>
          </ScrollArea>
        </CardContent>
      </Card>

      {/* Main: Details & Lists */}
      <div className="flex-1 flex flex-col gap-4 overflow-hidden">
        {selectedNode ? (
          <>
            <Card className="shadow-none border bg-card">
              <CardHeader className="p-4 flex flex-row items-center justify-between">
                <div className="flex items-center gap-3">
                  <div className="p-2 bg-primary/10 rounded-lg text-primary">
                    {React.createElement(getTenantIcon(selectedNode.type), {
                      size: 24,
                    })}
                  </div>
                  <div>
                    <CardTitle className="text-lg">
                      {selectedNode.name}
                    </CardTitle>
                    <CardDescription className="text-xs font-mono">
                      {selectedNode.slug} ({selectedNode.type})
                    </CardDescription>
                  </div>
                </div>
                <div className="flex items-center gap-2">
                  <Button
                    variant="outline"
                    size="sm"
                    onClick={() => navigate(`/tenants/${selectedNode.id}`)}
                  >
                    <ExternalLink size={14} className="mr-2" /> 상세보기
                  </Button>
                </div>
              </CardHeader>
            </Card>

            <div className="flex-1 grid grid-cols-2 gap-4 min-h-0">
              {/* Siblings (Same Depth) */}
              <Card className="flex flex-col shadow-none border overflow-hidden">
                <CardHeader className="p-3 bg-muted/10 border-b">
                  <CardTitle className="text-xs font-semibold">
                    동일 레벨 조직 ({siblings.length})
                  </CardTitle>
                </CardHeader>
                <CardContent className="p-0 flex-1 overflow-hidden">
                  <ScrollArea className="h-full">
                    <div className="p-2 space-y-1">
                      {siblings.map((s) => (
                        <div
                          key={s.id}
                          onClick={() => setSelectedId(s.id)}
                          onKeyUp={(e) => {
                            if (e.key === "Enter" || e.key === " ") {
                              setSelectedId(s.id);
                            }
                          }}
                          className={`flex items-center justify-between p-2 rounded-md cursor-pointer transition-colors border ${
                            s.id === selectedId
                              ? "bg-primary/5 border-primary/20"
                              : "hover:bg-muted border-transparent"
                          }`}
                        >
                          <div className="flex items-center gap-2 min-w-0">
                            {React.createElement(getTenantIcon(s.type), {
                              size: 14,
                              className: "text-muted-foreground",
                            })}
                            <span className="text-sm truncate">{s.name}</span>
                          </div>
                          <Badge variant="outline" className="text-[10px]">
                            {s.recursiveMemberCount}명
                          </Badge>
                        </div>
                      ))}
                    </div>
                  </ScrollArea>
                </CardContent>
              </Card>

              {/* Children */}
              <Card className="flex flex-col shadow-none border overflow-hidden">
                <CardHeader className="p-3 bg-muted/10 border-b">
                  <CardTitle className="text-xs font-semibold">
                    하위 조직 ({selectedNode.children.length})
                  </CardTitle>
                </CardHeader>
                <CardContent className="p-0 flex-1 overflow-hidden">
                  <ScrollArea className="h-full">
                    <div className="p-2 space-y-1">
                      {selectedNode.children.map((c) => (
                        <div
                          key={c.id}
                          onClick={() => setSelectedId(c.id)}
                          onKeyUp={(e) => {
                            if (e.key === "Enter" || e.key === " ") {
                              setSelectedId(c.id);
                            }
                          }}
                          className="flex items-center justify-between p-2 rounded-md hover:bg-muted cursor-pointer transition-colors border border-transparent"
                        >
                          <div className="flex items-center gap-2 min-w-0">
                            {React.createElement(getTenantIcon(c.type), {
                              size: 14,
                              className: "text-muted-foreground",
                            })}
                            <span className="text-sm truncate">{c.name}</span>
                          </div>
                          <Badge variant="outline" className="text-[10px]">
                            {c.recursiveMemberCount}명
                          </Badge>
                        </div>
                      ))}
                      {selectedNode.children.length === 0 && (
                        <div className="py-8 text-center text-xs text-muted-foreground opacity-50">
                          하위 조직이 없습니다.
                        </div>
                      )}
                    </div>
                  </ScrollArea>
                </CardContent>
              </Card>
            </div>
          </>
        ) : (
          <div className="flex-1 flex items-center justify-center border-2 border-dashed rounded-xl opacity-30">
            조직을 선택하세요.
          </div>
        )}
      </div>
    </div>
  );
};

export default TenantListPage;
