"use client";

/**
 * Member-module access: roles, and which console users hold them.
 *
 * Deliberately not part of role management — console roles grant the module,
 * these decide what inside it a user can reach and which slice of the member
 * base they see. Roles are the only unit of assignment; there is no per-user
 * capability editing, so a role's definition lives in exactly one place.
 */

import {
  ALL_CAPABILITIES,
  CAPABILITY_GROUPS,
  deleteMemberAccessRole,
  saveMemberAccessRole,
  type MemberAccessRole,
  type MemberAccessRow,
} from "@/lib/features/members/access";
import {
  ActionIcon,
  Badge,
  Button,
  Card,
  Group,
  Modal,
  MultiSelect,
  Stack,
  Switch,
  Text,
  TextInput,
  Tooltip,
} from "@mantine/core";
import {
  IconChevronRight,
  IconPencil,
  IconPlus,
  IconSearch,
  IconShieldLock,
  IconTrash,
  IconUserShield,
} from "@tabler/icons-react";
import { useMemo, useState } from "react";
import { Link, useNavigate, useRevalidator } from "react-router";
import { toast } from "sonner";
import {
  MemberDataTable,
  type MemberColumn,
} from "../_components/MemberDataTable";

type Option = { id: string; name: string };

const EMPTY_ROLE: Partial<MemberAccessRole> = { name: "" };

const MemberAccessClientPage: React.FC<{
  rows: MemberAccessRow[];
  accountTypes: Option[];
  accountStatuses: Option[];
  roles: MemberAccessRole[];
}> = ({ rows, accountTypes, accountStatuses, roles }) => {
  const navigate = useNavigate();
  const revalidator = useRevalidator();
  const [search, setSearch] = useState("");
  /*
   * Users whose console roles don't grant the Members module can't reach any of
   * this, so they're hidden by default — the list is about who it affects.
   */
  const [onlyModuleUsers, setOnlyModuleUsers] = useState(true);
  const [roleDraft, setRoleDraft] = useState<Partial<MemberAccessRole> | null>(
    null,
  );
  const [roleSaving, setRoleSaving] = useState(false);

  /** How many users hold each role — the question the roles card gets asked. */
  const assignedCounts = useMemo(() => {
    const counts = new Map<string, number>();
    for (const row of rows) {
      for (const role of row.roles ?? []) {
        counts.set(role.id, (counts.get(role.id) ?? 0) + 1);
      }
    }
    return counts;
  }, [rows]);

  const filtered = useMemo(() => {
    const term = search.trim().toLowerCase();
    return rows.filter((row) => {
      if (
        onlyModuleUsers &&
        !row.is_super_admin &&
        row.has_module_access === false
      ) {
        return false;
      }
      if (!term) return true;
      return [row.email, row.first_name, row.last_name]
        .filter(Boolean)
        .some((value) => String(value).toLowerCase().includes(term));
    });
  }, [rows, search, onlyModuleUsers]);

  const saveRole = async () => {
    if (!roleDraft?.name?.trim()) {
      toast.error("A role name is required");
      return;
    }
    setRoleSaving(true);
    const response = await saveMemberAccessRole(
      roleDraft as MemberAccessRole,
      roleDraft.id,
    );
    setRoleSaving(false);
    if (response?.success) {
      toast.success(roleDraft.id ? "Role updated" : "Role created");
      setRoleDraft(null);
      revalidator.revalidate();
    } else {
      toast.error(response?.message ?? "Failed to save role");
    }
  };

  const removeRole = async (role: MemberAccessRole) => {
    const holders = assignedCounts.get(role.id) ?? 0;
    const response = await deleteMemberAccessRole(role.id);
    if (response?.success) {
      // Say what it cost: deleting a role revokes it from everyone holding it.
      toast.success(
        holders > 0
          ? `${role.name} deleted · revoked from ${holders} user(s)`
          : `${role.name} deleted`,
      );
      revalidator.revalidate();
    } else {
      toast.error(response?.message ?? "Failed to delete role");
    }
  };

  const roleColumns: MemberColumn<MemberAccessRole>[] = [
    {
      key: "name",
      header: "Role",
      render: (role) => (
        <Stack gap={0}>
          <Text fz="sm" fw={600}>
            {role.name}
          </Text>
          {role.description ? (
            <Text fz={11} c="dimmed" lineClamp={1}>
              {role.description}
            </Text>
          ) : null}
        </Stack>
      ),
    },
    {
      key: "users",
      header: "Users",
      numeric: true,
      render: (role) => {
        const holders = assignedCounts.get(role.id) ?? 0;
        return (
          <Text fz="sm" c={holders ? undefined : "dimmed"}>
            {holders}
          </Text>
        );
      },
    },
    // One column per group: counts compare across roles at a glance, and the
    // specifics are a hover away instead of printed into every row.
    ...CAPABILITY_GROUPS.map((group) => ({
      key: group.key,
      header: group.title,
      render: (role: MemberAccessRole) => {
        const granted = group.capabilities.filter(
          (capability) => role[capability.key] === true,
        );
        if (granted.length === 0) {
          return (
            <Text fz="sm" c="dimmed">
              —
            </Text>
          );
        }
        return (
          <Tooltip label={granted.map((entry) => entry.label).join(", ")}>
            <Badge
              variant="light"
              color={group.key === "actions" ? "orange" : undefined}
              size="sm"
              radius={4}
            >
              {granted.length} of {group.capabilities.length}
            </Badge>
          </Tooltip>
        );
      },
    })),
    {
      key: "slice",
      header: "Visible Slice",
      render: (role) => {
        const types = role.account_type_ids?.length ?? 0;
        const statuses = role.account_status_ids?.length ?? 0;
        return types || statuses ? (
          <Tooltip
            label={`${types || "all"} account type(s) · ${statuses || "all"
              } status(es)`}
          >
            <Badge variant="light" color="gray" size="sm" radius={4}>
              scoped
            </Badge>
          </Tooltip>
        ) : (
          <Text fz="sm" c="dimmed">
            All accounts
          </Text>
        );
      },
    },
    {
      key: "roleActions",
      header: "",
      onCellClick: "stop",
      width: 90,
      render: (role) => {
        const holders = assignedCounts.get(role.id) ?? 0;
        return (
          <Group gap={4} wrap="nowrap">
            <Tooltip label="Edit role">
              <ActionIcon
                variant="subtle"
                onClick={() => setRoleDraft({ ...role })}
                aria-label={`Edit ${role.name}`}
              >
                <IconPencil size={15} />
              </ActionIcon>
            </Tooltip>
            <Tooltip
              label={
                holders > 0
                  ? `Delete — revokes from ${holders} user(s)`
                  : "Delete role"
              }
            >
              <ActionIcon
                variant="subtle"
                color="red"
                onClick={() => void removeRole(role)}
                aria-label={`Delete ${role.name}`}
              >
                <IconTrash size={15} />
              </ActionIcon>
            </Tooltip>
          </Group>
        );
      },
    },
  ];

  const columns: MemberColumn<MemberAccessRow>[] = [
    {
      key: "user",
      header: "Console User",
      render: (row) => (
        <Stack gap={0}>
          <Group gap={6} wrap="nowrap">
            <Text fz="sm" fw={500}>
              {[row.first_name, row.last_name].filter(Boolean).join(" ") || "—"}
            </Text>
            {row.is_super_admin ? (
              <Badge variant="light" size="xs" radius={4}>
                super admin
              </Badge>
            ) : null}
          </Group>
          <Text fz={11} c="dimmed">
            {row.email}
          </Text>
        </Stack>
      ),
    },
    {
      key: "module",
      header: "Members Module",
      render: (row) =>
        row.is_super_admin || row.has_module_access ? (
          <Badge variant="light" size="sm" radius={4}>
            granted
          </Badge>
        ) : (
          <Tooltip label="No console role grants the Members module, so nothing here takes effect">
            <Badge variant="light" color="orange" size="sm" radius={4}>
              no role
            </Badge>
          </Tooltip>
        ),
    },
    {
      key: "roles",
      header: "Access Roles",
      render: (row) => {
        if (row.is_super_admin) {
          return (
            <Text fz="sm" c="dimmed">
              all capabilities
            </Text>
          );
        }
        if (!row.roles?.length) {
          return (
            <Text fz="sm" c="dimmed">
              None assigned
            </Text>
          );
        }
        return (
          <Group gap={4} wrap="wrap">
            {row.roles.map((role) => (
              <Badge key={role.id} variant="light" size="sm" radius={4}>
                {role.name}
              </Badge>
            ))}
          </Group>
        );
      },
    },
    {
      key: "capabilities",
      header: "Areas",
      numeric: true,
      render: (row) => {
        if (row.is_super_admin) {
          return <Text fz="sm">{ALL_CAPABILITIES.length}</Text>;
        }
        // Union of the assigned roles — the same rule the server applies.
        const granted = ALL_CAPABILITIES.filter((capability) =>
          row.roles?.some(
            (assigned) =>
              roles.find((entry) => entry.id === assigned.id)?.[
              capability.key
              ] === true,
          ),
        );
        return (
          <Tooltip
            label={
              granted.length
                ? granted.map((entry) => entry.label).join(", ")
                : "No areas granted"
            }
          >
            <Text fz="sm" c={granted.length ? undefined : "dimmed"}>
              {granted.length} of {ALL_CAPABILITIES.length}
            </Text>
          </Tooltip>
        );
      },
    },
    {
      key: "scope",
      header: "Visible Slice",
      render: (row) => {
        if (row.is_super_admin) {
          return (
            <Text fz="sm" c="dimmed">
              All accounts
            </Text>
          );
        }
        const assignedRoles = (row.roles ?? [])
          .map((assigned) => roles.find((entry) => entry.id === assigned.id))
          .filter(Boolean) as MemberAccessRole[];
        // A role with no slice keeps access unrestricted, so an empty list wins.
        const unrestricted =
          assignedRoles.length === 0 ||
          assignedRoles.some(
            (role) =>
              (role.account_type_ids?.length ?? 0) === 0 &&
              (role.account_status_ids?.length ?? 0) === 0,
          );
        return unrestricted ? (
          <Text fz="sm" c="dimmed">
            All accounts
          </Text>
        ) : (
          <Badge variant="light" color="gray" size="sm" radius={4}>
            scoped
          </Badge>
        );
      },
    },
    {
      key: "actions",
      header: "",
      onCellClick: "stop",
      width: 120,
      render: (row) => (
        <Button
          size="compact-xs"
          variant="light"
          rightSection={<IconChevronRight size={12} />}
          component={Link}
          to={`/admin/members/access/${row.console_user_id}`}
        >
          Manage
        </Button>
      ),
    },
  ];

  return (
    <Stack gap="lg">
      <Group justify="space-between" align="center">
        <Group gap={8} align="center">
          <IconShieldLock size={18} />
          <Text fw={700} fz={15}>
            Member Module Access
          </Text>
          <Badge variant="light" size="sm" radius="sm">
            {roles.length} role(s)
          </Badge>
        </Group>
        <Button
          leftSection={<IconPlus size={16} />}
          onClick={() => setRoleDraft({ ...EMPTY_ROLE })}
        >
          New role
        </Button>
      </Group>

      {/* Roles first: nothing can be assigned until one exists. */}
      {roles.length === 0 ? (
        <Card withBorder radius="lg" p="xl">
          <Stack gap={6} align="center">
            <IconUserShield size={22} />
            <Text fw={600} fz={14}>
              No access roles yet
            </Text>
            <Text fz={12} c="dimmed" ta="center" maw={420}>
              A role bundles the areas of the member module a user may open, and
              optionally limits which accounts they can see. Assign roles to
              users below.
            </Text>
            <Button
              mt={4}
              size="sm"
              leftSection={<IconPlus size={15} />}
              onClick={() => setRoleDraft({ ...EMPTY_ROLE })}
            >
              Create the first role
            </Button>
          </Stack>
        </Card>
      ) : (
        <Card withBorder radius="lg" p="md">
          <Text fw={600} fz={14} mb="sm">
            Access roles
          </Text>
          {/*
           * A table, not cards: listing every capability per role produced a
           * wall of badges that couldn't be compared across roles. Here a role
           * is one row and each area group is a count, with the specifics on
           * hover. Row click opens the editor.
           */}
          <MemberDataTable
            rows={roles}
            columns={roleColumns}
            minWidth={900}
            rowKey={(role) => role.id}
            onRowClick={(role) => setRoleDraft({ ...role })}
            emptyMessage="No access roles"
          />
        </Card>
      )}

      <Card withBorder radius="lg" p="md">
        <Group justify="space-between" align="center" mb="sm">
          <Group gap={8} align="baseline">
            <Text fw={600} fz={14}>
              Console users
            </Text>
            <Text fz={12} c="dimmed">
              {filtered.length} of {rows.length} active
            </Text>
          </Group>
          <Group gap="sm">
            <Switch
              size="xs"
              label="Only users with module access"
              checked={onlyModuleUsers}
              onChange={(event) => {
                const checked = event.currentTarget.checked;
                setOnlyModuleUsers(checked);
              }}
            />
            <TextInput
              size="sm"
              w={240}
              placeholder="Search console user"
              leftSection={<IconSearch size={14} />}
              value={search}
              onChange={(event) => setSearch(event.currentTarget.value)}
            />
          </Group>
        </Group>
        <MemberDataTable
          rows={filtered}
          columns={columns}
          minWidth={1100}
          rowKey={(row) => row.console_user_id}
          onRowClick={(row) =>
            navigate(`/admin/members/access/${row.console_user_id}`)
          }
          emptyMessage="No console users match"
        />
      </Card>

      <Modal
        opened={Boolean(roleDraft)}
        onClose={() => setRoleDraft(null)}
        title={roleDraft?.id ? `Edit ${roleDraft.name}` : "New access role"}
        size="lg"
      >
        <Stack gap="md">
          <TextInput
            label="Role name"
            placeholder="e.g. Member Support"
            value={roleDraft?.name ?? ""}
            onChange={(event) => {
              const name = event.currentTarget.value;
              setRoleDraft((current) => ({ ...(current ?? {}), name }));
            }}
          />
          <TextInput
            label="Description"
            placeholder="What this role is for"
            value={roleDraft?.description ?? ""}
            onChange={(event) => {
              const description = event.currentTarget.value;
              setRoleDraft((current) => ({ ...(current ?? {}), description }));
            }}
          />

          {CAPABILITY_GROUPS.map((group) => (
            <Stack key={group.key} gap={6}>
              <Group gap={6} align="center">
                <Text fw={500} size="sm">
                  {group.title}
                </Text>
                {group.key === "actions" ? (
                  <Badge variant="light" color="orange" size="xs" radius={4}>
                    writes data
                  </Badge>
                ) : null}
              </Group>
              {group.capabilities.map((capability) => (
                <Switch
                  key={capability.key}
                  label={capability.label}
                  description={capability.hint}
                  checked={Boolean(roleDraft?.[capability.key])}
                  onChange={(event) => {
                    const checked = event.currentTarget.checked;
                    setRoleDraft((current) => ({
                      ...(current ?? {}),
                      [capability.key]: checked,
                    }));
                  }}
                />
              ))}
            </Stack>
          ))}

          <MultiSelect
            label="Account types visible"
            description="Leave empty for all account types"
            data={accountTypes.map((type) => ({
              value: type.id,
              label: type.name,
            }))}
            value={roleDraft?.account_type_ids ?? []}
            onChange={(next) =>
              setRoleDraft((current) => ({
                ...(current ?? {}),
                account_type_ids: next,
              }))
            }
            searchable
            clearable
          />
          <MultiSelect
            label="Account statuses visible"
            description="Leave empty for all statuses"
            data={accountStatuses.map((status) => ({
              value: status.id,
              label: status.name,
            }))}
            value={roleDraft?.account_status_ids ?? []}
            onChange={(next) =>
              setRoleDraft((current) => ({
                ...(current ?? {}),
                account_status_ids: next,
              }))
            }
            searchable
            clearable
          />

          <Group justify="flex-end">
            <Button variant="subtle" onClick={() => setRoleDraft(null)}>
              Cancel
            </Button>
            <Button loading={roleSaving} onClick={() => void saveRole()}>
              Save role
            </Button>
          </Group>
        </Stack>
      </Modal>
    </Stack>
  );
};

export default MemberAccessClientPage;
