import { useQuery } from "@tanstack/react-query";
import { Check, ChevronDown, ChevronRight, Search, X } from "lucide-react";
import * as React from "react";
import { useLocation } from "react-router-dom";
import { Button } from "../../../components/ui/button";
import {
  fetchOrgChartSnapshot,
  fetchPublicOrgChart,
} from "../../../lib/adminApi";
import { buildOrgPickerTree, flattenDescendants } from "../pickerTree";
import {
  buildOrgPickerEmbedSrc,
  nodeKey,
  type OrgPickerEmbedOptions,
  type OrgPickerMode,
  type OrgPickerResult,
  type OrgPickerSelectableType,
  type OrgPickerSelection,
  type OrgPickerTreeNode,
  parseOrgPickerEmbedOptions,
  parseOrgPickerMode,
  parseOrgPickerSelectableType,
} from "../pickerTypes";

function canSelectNode(
  node: OrgPickerTreeNode,
  select: OrgPickerSelectableType,
) {
  return select === "both" || select === node.type;
}

function canToggleNode(
  node: OrgPickerTreeNode,
  mode: OrgPickerMode,
  select: OrgPickerSelectableType,
) {
  return (
    canSelectNode(node, select) ||
    (mode === "multiple" && select === "user" && node.type === "tenant")
  );
}

function toSelection(node: OrgPickerTreeNode): OrgPickerSelection {
  if (node.type === "user") {
    return {
      type: node.type,
      id: node.id,
      name: node.name,
      email: node.user?.email,
    };
  }

  return {
    type: node.type,
    id: node.id,
    name: node.name,
  };
}

function collectSelectedNodes({
  roots,
  selectedKeys,
  includeDescendants,
  select,
}: {
  roots: OrgPickerTreeNode[];
  selectedKeys: Set<string>;
  includeDescendants: boolean;
  select: OrgPickerSelectableType;
}) {
  const selected = new Map<string, OrgPickerTreeNode>();
  const visit = (node: OrgPickerTreeNode) => {
    const key = nodeKey(node);
    if (selectedKeys.has(key)) {
      if (canSelectNode(node, select)) {
        selected.set(key, node);
      }
      if (includeDescendants && node.type === "tenant") {
        for (const descendant of flattenDescendants(node)) {
          if (canSelectNode(descendant, select)) {
            selected.set(nodeKey(descendant), descendant);
          }
        }
      }
    }

    for (const child of node.children) visit(child);
  };

  for (const root of roots) visit(root);
  return Array.from(selected.values()).map(toSelection);
}

function collectCheckedKeys({
  roots,
  selectedKeys,
  includeDescendants,
  select,
}: {
  roots: OrgPickerTreeNode[];
  selectedKeys: Set<string>;
  includeDescendants: boolean;
  select: OrgPickerSelectableType;
}) {
  const checkedKeys = new Set(selectedKeys);
  if (!includeDescendants) return checkedKeys;

  const visit = (node: OrgPickerTreeNode) => {
    const key = nodeKey(node);
    if (selectedKeys.has(key) && node.type === "tenant") {
      for (const descendant of flattenDescendants(node)) {
        if (canSelectNode(descendant, select)) {
          checkedKeys.add(nodeKey(descendant));
        }
      }
    }

    for (const child of node.children) visit(child);
  };

  for (const root of roots) visit(root);
  return checkedKeys;
}

function postPickerMessage(message: unknown) {
  window.parent.postMessage(message, "*");
}

function collectSearchValues(value: unknown, depth = 0): string[] {
  if (value == null || depth > 4) return [];
  if (
    typeof value === "string" ||
    typeof value === "number" ||
    typeof value === "boolean"
  ) {
    return [String(value)];
  }
  if (Array.isArray(value)) {
    return value.flatMap((item) => collectSearchValues(item, depth + 1));
  }
  if (typeof value === "object") {
    return Object.entries(value as Record<string, unknown>).flatMap(
      ([key, item]) => [key, ...collectSearchValues(item, depth + 1)],
    );
  }
  return [];
}

function getNodeSearchValues(node: OrgPickerTreeNode) {
  const tenantSearchValues = node.tenant
    ? collectSearchValues({
        id: node.tenant.id,
        type: node.tenant.type,
        name: node.tenant.name,
        slug: node.tenant.slug,
        description: node.tenant.description,
        status: node.tenant.status,
        domains: node.tenant.domains,
        parentId: node.tenant.parentId,
        config: node.tenant.config,
        memberCount: node.tenant.memberCount,
        createdAt: node.tenant.createdAt,
        updatedAt: node.tenant.updatedAt,
      })
    : [];

  return [
    node.type,
    node.id,
    node.name,
    node.parentId ?? "",
    ...tenantSearchValues,
    ...collectSearchValues(node.user),
  ].map((value) => value.toLowerCase());
}

function nodeMatchesSearch(node: OrgPickerTreeNode, query: string) {
  return getNodeSearchValues(node).some((value) => value.includes(query));
}

function filterPickerTree(
  roots: OrgPickerTreeNode[],
  rawQuery: string,
): OrgPickerTreeNode[] {
  const query = rawQuery.trim().toLowerCase();
  if (!query) return roots;

  const filterNode = (node: OrgPickerTreeNode): OrgPickerTreeNode | null => {
    const filteredChildren = node.children
      .map(filterNode)
      .filter((child): child is OrgPickerTreeNode => Boolean(child));

    if (nodeMatchesSearch(node, query) || filteredChildren.length > 0) {
      return {
        ...node,
        children: filteredChildren,
      };
    }
    return null;
  };

  return roots
    .map(filterNode)
    .filter((node): node is OrgPickerTreeNode => Boolean(node));
}

function OrgPickerTree({
  roots,
  mode,
  select,
  selectedKeys,
  onSingleSelect,
  onToggle,
}: {
  roots: OrgPickerTreeNode[];
  mode: OrgPickerMode;
  select: OrgPickerSelectableType;
  selectedKeys: Set<string>;
  onSingleSelect: (node: OrgPickerTreeNode) => void;
  onToggle: (node: OrgPickerTreeNode, checked: boolean) => void;
}) {
  return (
    <div className="space-y-1" data-testid="org-picker-tree">
      {roots.map((node) => (
        <OrgPickerTreeItem
          key={nodeKey(node)}
          mode={mode}
          node={node}
          onSingleSelect={onSingleSelect}
          onToggle={onToggle}
          select={select}
          selectedKeys={selectedKeys}
        />
      ))}
    </div>
  );
}

function OrgPickerTreeItem({
  node,
  mode,
  select,
  selectedKeys,
  onSingleSelect,
  onToggle,
  depth = 0,
}: {
  node: OrgPickerTreeNode;
  mode: OrgPickerMode;
  select: OrgPickerSelectableType;
  selectedKeys: Set<string>;
  onSingleSelect: (node: OrgPickerTreeNode) => void;
  onToggle: (node: OrgPickerTreeNode, checked: boolean) => void;
  depth?: number;
}) {
  const [isOpen, setIsOpen] = React.useState(true);
  const selectable = canSelectNode(node, select);
  const toggleable = canToggleNode(node, mode, select);
  const hasChildren = node.children.length > 0;
  const key = nodeKey(node);
  const checked = selectedKeys.has(key);
  const label = `${node.name} 선택`;
  const email = node.type === "user" ? node.user?.email : undefined;
  const nameTestId =
    node.type === "tenant"
      ? "org-picker-node-name-tenant"
      : "org-picker-node-name-user";
  const content = (
    <span className="flex min-w-0 flex-col">
      <span
        className={`truncate font-semibold leading-5 ${
          node.type === "tenant" ? "text-[#0a2114]" : ""
        }`}
        data-testid={nameTestId}
      >
        {node.name}
      </span>
      {email ? (
        <span className="truncate text-xs leading-5 text-muted-foreground">
          {email}
        </span>
      ) : null}
    </span>
  );

  return (
    <div className="relative">
      <div
        className={`group flex min-h-7 items-center gap-1.5 rounded-sm py-0.5 pr-1.5 transition ${
          mode === "single" && checked
            ? "bg-primary/15 text-foreground ring-2 ring-primary/60 shadow-sm"
            : "hover:bg-secondary/50"
        } ${depth > 0 ? "pl-4" : "pl-1"}`}
        data-selected={mode === "single" && checked ? "true" : undefined}
      >
        {hasChildren ? (
          <button
            type="button"
            className="grid h-6 w-6 shrink-0 place-items-center rounded-sm text-muted-foreground transition hover:bg-secondary"
            onClick={() => setIsOpen((current) => !current)}
            aria-label={`${node.name} ${isOpen ? "접기" : "펼치기"}`}
          >
            {isOpen ? <ChevronDown size={16} /> : <ChevronRight size={16} />}
          </button>
        ) : (
          <span className="h-6 w-6 shrink-0" aria-hidden="true" />
        )}

        {mode === "multiple" && toggleable ? (
          <input
            aria-label={label}
            checked={checked}
            className="h-3.5 w-3.5 rounded border-border"
            onChange={(event) => onToggle(node, event.target.checked)}
            type="checkbox"
          />
        ) : null}

        {mode === "single" && selectable ? (
          <button
            type="button"
            aria-pressed={checked}
            className={`min-w-0 flex-1 rounded-sm px-1 text-left outline-none transition focus-visible:ring-2 focus-visible:ring-ring ${
              checked ? "text-primary" : ""
            }`}
            data-selected={checked ? "true" : undefined}
            onClick={() => onSingleSelect(node)}
          >
            {content}
          </button>
        ) : (
          <div className="min-w-0 flex-1">{content}</div>
        )}
      </div>

      {isOpen && hasChildren ? (
        <div className="ml-4">
          {node.children.map((child) => (
            <OrgPickerTreeItem
              depth={depth + 1}
              key={nodeKey(child)}
              mode={mode}
              node={child}
              onSingleSelect={onSingleSelect}
              onToggle={onToggle}
              select={select}
              selectedKeys={selectedKeys}
            />
          ))}
        </div>
      ) : null}
    </div>
  );
}

export function OrgPickerEmbedPage() {
  const location = useLocation();
  const searchParams = new URLSearchParams(location.search);
  const mode = parseOrgPickerMode(searchParams.get("mode"));
  const select = parseOrgPickerSelectableType(searchParams.get("select"));
  const rootTenantId = searchParams.get("rootTenantId") || undefined;
  const shareToken = searchParams.get("token") || undefined;
  const includeInternal = searchParams.get("includeInternal") === "true";
  const tenantId =
    searchParams.get("tenantSlug") ||
    searchParams.get("tenantId") ||
    searchParams.get("companyTenantId") ||
    undefined;
  const [includeDescendants, setIncludeDescendants] = React.useState(
    searchParams.get("includeDescendants") !== "false",
  );
  const showDescendantToggle =
    searchParams.get("showDescendantToggle") !== "false";
  const [searchQuery, setSearchQuery] = React.useState("");
  const [selectedKeys, setSelectedKeys] = React.useState<Set<string>>(
    () => new Set(),
  );

  const orgChartQuery = useQuery({
    queryKey: [
      "org-picker-orgchart",
      shareToken ? "public" : "authenticated",
      shareToken,
    ],
    queryFn: () =>
      shareToken ? fetchPublicOrgChart(shareToken) : fetchOrgChartSnapshot(),
  });

  React.useEffect(() => {
    postPickerMessage({ type: "orgfront:picker:ready" });
  }, []);

  const tree = React.useMemo(() => {
    return buildOrgPickerTree({
      includeInternal,
      tenants: orgChartQuery.data?.tenants ?? [],
      users: select === "tenant" ? [] : (orgChartQuery.data?.users ?? []),
      rootTenantId,
      tenantId,
    });
  }, [includeInternal, orgChartQuery.data, rootTenantId, select, tenantId]);

  const selectedItems = React.useMemo(
    () =>
      collectSelectedNodes({
        roots: tree.roots,
        selectedKeys,
        includeDescendants: mode === "multiple" && includeDescendants,
        select,
      }),
    [includeDescendants, mode, select, selectedKeys, tree.roots],
  );
  const checkedKeys = React.useMemo(
    () =>
      collectCheckedKeys({
        roots: tree.roots,
        selectedKeys,
        includeDescendants: mode === "multiple" && includeDescendants,
        select,
      }),
    [includeDescendants, mode, select, selectedKeys, tree.roots],
  );
  const filteredRoots = React.useMemo(
    () => filterPickerTree(tree.roots, searchQuery),
    [searchQuery, tree.roots],
  );

  const handleSingleSelect = (node: OrgPickerTreeNode) => {
    setSelectedKeys(new Set([nodeKey(node)]));
  };

  const handleToggle = (node: OrgPickerTreeNode, checked: boolean) => {
    setSelectedKeys((current) => {
      const next = new Set(current);
      const key = nodeKey(node);
      if (checked) next.add(key);
      else next.delete(key);
      return next;
    });
  };

  const confirmSelection = () => {
    const payload: OrgPickerResult = {
      mode,
      selections: selectedItems,
    };
    postPickerMessage({ type: "orgfront:picker:confirm", payload });
  };

  const cancelSelection = () => {
    postPickerMessage({ type: "orgfront:picker:cancel" });
  };

  const isLoading = orgChartQuery.isLoading;
  const isError = orgChartQuery.isError;

  React.useEffect(() => {
    const htmlOverflow = document.documentElement.style.overflow;
    const bodyOverflow = document.body.style.overflow;
    document.documentElement.style.overflow = "hidden";
    document.body.style.overflow = "hidden";

    return () => {
      document.documentElement.style.overflow = htmlOverflow;
      document.body.style.overflow = bodyOverflow;
    };
  }, []);

  React.useEffect(() => {
    if (!isError) return;
    postPickerMessage({
      type: "orgfront:picker:error",
      error: "org_picker_load_failed",
    });
  }, [isError]);

  if (isLoading) {
    return (
      <div className="grid min-h-screen place-items-center bg-background p-6 text-muted-foreground">
        조직 선택기를 불러오는 중...
      </div>
    );
  }

  if (isError) {
    return (
      <div className="grid min-h-screen place-items-center bg-background p-6 text-destructive">
        조직 선택기를 불러올 수 없습니다.
      </div>
    );
  }

  return (
    <div className="flex h-screen flex-col overflow-hidden bg-background text-foreground">
      <main className="flex min-h-0 flex-1 flex-col">
        <div
          className="shrink-0 border-b border-border bg-background p-2"
          data-testid="org-picker-search-section"
        >
          <div className="grid grid-cols-[minmax(0,1fr),auto] items-end gap-2">
            <div>
              <label className="sr-only" htmlFor="org-picker-search">
                조직/구성원 검색
              </label>
              <div className="relative">
                <Search
                  aria-hidden="true"
                  className="pointer-events-none absolute left-3 top-1/2 -translate-y-1/2 text-muted-foreground"
                  size={16}
                />
                <input
                  id="org-picker-search"
                  className="h-9 w-full rounded-md border border-input bg-background pl-9 pr-3 text-sm"
                  onChange={(event) => setSearchQuery(event.target.value)}
                  placeholder="ID, 이름, 이메일, 메타데이터"
                  type="search"
                  value={searchQuery}
                />
              </div>
            </div>

            {mode === "multiple" && showDescendantToggle ? (
              <label
                className="inline-flex h-9 items-center gap-2 whitespace-nowrap text-sm"
                data-testid="org-picker-descendant-toggle"
              >
                <input
                  checked={includeDescendants}
                  className="h-3.5 w-3.5"
                  onChange={(event) =>
                    setIncludeDescendants(event.target.checked)
                  }
                  type="checkbox"
                />
                <span>하위 선택</span>
              </label>
            ) : null}
          </div>
        </div>

        <div
          className="min-h-0 flex-1 overflow-y-auto p-3"
          data-testid="org-picker-tree-scroll"
        >
          {filteredRoots.length > 0 ? (
            <OrgPickerTree
              mode={mode}
              onSingleSelect={handleSingleSelect}
              onToggle={handleToggle}
              roots={filteredRoots}
              select={select}
              selectedKeys={checkedKeys}
            />
          ) : (
            <div className="grid min-h-40 place-items-center rounded-md border border-dashed border-border bg-background p-6 text-center text-sm text-muted-foreground">
              검색 결과가 없습니다.
            </div>
          )}
        </div>
      </main>

      <footer className="flex shrink-0 items-center justify-between gap-3 border-t border-border bg-background px-3 py-2">
        <div className="min-w-0 text-sm text-muted-foreground">
          {selectedItems.length > 0
            ? `${selectedItems.length}개 항목 선택됨`
            : "선택된 항목이 없습니다."}
        </div>
        <div className="flex items-center gap-2">
          <Button onClick={cancelSelection} type="button" variant="outline">
            <X size={16} />
            취소
          </Button>
          <Button
            disabled={selectedItems.length === 0}
            onClick={confirmSelection}
            type="button"
          >
            <Check size={16} />
            선택 완료
          </Button>
        </div>
      </footer>
    </div>
  );
}

export function OrgPickerPage() {
  const location = useLocation();
  const shareToken = new URLSearchParams(location.search).get("token");
  const [options, setOptions] = React.useState<OrgPickerEmbedOptions>(() =>
    parseOrgPickerEmbedOptions(location.search),
  );
  const pickerSrcBase = buildOrgPickerEmbedSrc(options);
  const pickerSrc = shareToken
    ? `${pickerSrcBase}&token=${encodeURIComponent(shareToken)}`
    : pickerSrcBase;

  return (
    <div className="space-y-4">
      <header className="flex flex-col gap-2 md:flex-row md:items-end md:justify-between">
        <div>
          <p className="text-xs font-semibold uppercase tracking-[0.18em] text-muted-foreground">
            Picker Workbench
          </p>
          <h1 className="text-2xl font-semibold">조직 선택기</h1>
        </div>
        <div className="rounded-md border border-border bg-card px-3 py-2 text-sm text-muted-foreground">
          {pickerSrc}
        </div>
      </header>

      <section className="grid gap-3 rounded-md border border-border bg-card p-4 md:grid-cols-2 lg:grid-cols-[1fr,1fr,1fr,auto,auto,auto,auto,auto] lg:items-end">
        <label className="space-y-1 text-sm font-medium">
          <span className="block text-muted-foreground">선택 모드</span>
          <select
            className="h-10 w-full rounded-md border border-input bg-background px-3"
            value={options.mode}
            onChange={(event) =>
              setOptions((current) => ({
                ...current,
                mode: event.target.value as OrgPickerMode,
              }))
            }
          >
            <option value="multiple">복수 선택</option>
            <option value="single">단일 선택</option>
          </select>
        </label>

        <label className="space-y-1 text-sm font-medium">
          <span className="block text-muted-foreground">선택 대상</span>
          <select
            className="h-10 w-full rounded-md border border-input bg-background px-3"
            value={options.select}
            onChange={(event) =>
              setOptions((current) => ({
                ...current,
                select: event.target.value as OrgPickerSelectableType,
              }))
            }
          >
            <option value="both">조직&구성원</option>
            <option value="tenant">조직</option>
            <option value="user">구성원</option>
          </select>
        </label>

        <label className="space-y-1 text-sm font-medium">
          <span className="block text-muted-foreground">tenant slug</span>
          <input
            className="h-10 w-full rounded-md border border-input bg-background px-3"
            onChange={(event) =>
              setOptions((current) => ({
                ...current,
                tenantId: event.target.value,
              }))
            }
            placeholder="saman"
            type="text"
            value={options.tenantId}
          />
        </label>

        <label className="flex h-10 items-center gap-2 rounded-md border border-border bg-background px-3 text-sm">
          <input
            checked={options.includeDescendants}
            disabled={options.mode === "single"}
            onChange={(event) =>
              setOptions((current) => ({
                ...current,
                includeDescendants: event.target.checked,
              }))
            }
            type="checkbox"
          />
          <span>하위 포함</span>
        </label>

        <label className="flex h-10 items-center gap-2 rounded-md border border-border bg-background px-3 text-sm">
          <input
            checked={options.showDescendantToggle}
            disabled={options.mode === "single"}
            onChange={(event) =>
              setOptions((current) => ({
                ...current,
                showDescendantToggle: event.target.checked,
              }))
            }
            type="checkbox"
          />
          <span>하위 선택 스위치 표시</span>
        </label>

        <label className="flex h-10 items-center gap-2 rounded-md border border-border bg-background px-3 text-sm">
          <input
            checked={options.includeInternal}
            onChange={(event) =>
              setOptions((current) => ({
                ...current,
                includeInternal: event.target.checked,
              }))
            }
            type="checkbox"
          />
          <span>internal 표시</span>
        </label>

        <label className="space-y-1 text-sm font-medium">
          <span className="block text-muted-foreground">임베딩 너비</span>
          <input
            className="h-10 w-full rounded-md border border-input bg-background px-3"
            max={1600}
            min={240}
            onChange={(event) =>
              setOptions((current) => ({
                ...current,
                width: Number.parseInt(event.target.value || "400", 10),
              }))
            }
            type="number"
            value={options.width}
          />
        </label>

        <label className="space-y-1 text-sm font-medium">
          <span className="block text-muted-foreground">임베딩 높이</span>
          <input
            className="h-10 w-full rounded-md border border-input bg-background px-3"
            max={1600}
            min={240}
            onChange={(event) =>
              setOptions((current) => ({
                ...current,
                height: Number.parseInt(event.target.value || "600", 10),
              }))
            }
            type="number"
            value={options.height}
          />
        </label>
      </section>

      <div
        className="max-w-full resize overflow-auto rounded-md border border-border bg-card"
        style={{
          width: options.width,
          height: options.height,
        }}
      >
        <iframe
          className="h-full w-full bg-background"
          src={pickerSrc}
          title="조직 선택기 테스트"
        />
      </div>
    </div>
  );
}
