import { createFileRoute } from "@tanstack/react-router";
import { useState } from "react";
import { Exit } from "effect";
import * as AsyncResult from "effect/unstable/reactivity/AsyncResult";
import { useAtom, useAtomValue } from "@effect/atom-react";
import { toast } from "@executor-js/react/components/sonner";

import { Button } from "@executor-js/react/components/button";
import { CopyButton } from "@executor-js/react/components/copy-button";
import { Input } from "@executor-js/react/components/input";
import { Label } from "@executor-js/react/components/label";
import { NativeSelect, NativeSelectOption } from "@executor-js/react/components/native-select";
import { useExecutorDocumentTitle } from "@executor-js/react/lib/document-title";
import {
  orgMembersAtom,
  removeMember,
  updateMemberRole,
} from "@executor-js/react/api/account-atoms";
import { orgMemberWriteKeys } from "@executor-js/react/api/reactivity-keys";

import { createInvite, invitesAtom, inviteWriteKeys, revokeInvite } from "../../admin-atoms";

export const Route = createFileRoute("/{-$orgSlug}/admin")({
  component: AdminPage,
});

const ROLES = ["member", "admin"] as const;

// Instance admin console. Members reuse the shared account atoms; invite codes
// are the self-host join mechanism. The API gates to owner/admin, so a
// non-admin who opens this just sees load failures.
function AdminPage() {
  useExecutorDocumentTitle("Admin");
  return (
    <div className="min-h-0 flex-1 overflow-y-auto">
      <div className="mx-auto flex max-w-3xl flex-col gap-10 px-6 py-10 lg:px-8 lg:py-14">
        <header className="space-y-1">
          <h1 className="font-display text-3xl tracking-tight text-foreground">Admin</h1>
          <p className="text-sm text-muted-foreground">
            Manage members and invite links for this instance.
          </p>
        </header>
        <MembersSection />
        <InvitesSection />
      </div>
    </div>
  );
}

function MembersSection() {
  const result = useAtomValue(orgMembersAtom);
  const [roleState, doUpdateRole] = useAtom(updateMemberRole, { mode: "promiseExit" });
  const [removeState, doRemove] = useAtom(removeMember, { mode: "promiseExit" });
  // The mutation atoms carry their own in-flight state — no manual busy tracking.
  const busy = AsyncResult.isWaiting(roleState) || AsyncResult.isWaiting(removeState);

  const changeRole = async (membershipId: string, roleSlug: string) => {
    const exit = await doUpdateRole({
      params: { membershipId },
      payload: { roleSlug },
      reactivityKeys: orgMemberWriteKeys,
    });
    toast[Exit.isSuccess(exit) ? "success" : "error"](
      Exit.isSuccess(exit) ? "Role updated" : "Failed to update role",
    );
  };

  const remove = async (membershipId: string, label: string) => {
    const exit = await doRemove({ params: { membershipId }, reactivityKeys: orgMemberWriteKeys });
    toast[Exit.isSuccess(exit) ? "success" : "error"](
      Exit.isSuccess(exit) ? `Removed ${label}` : "Failed to remove member",
    );
  };

  return (
    <section className="space-y-3">
      <h2 className="text-sm font-medium text-foreground">Members</h2>
      {AsyncResult.match(result, {
        onInitial: () => <Notice>Loading members…</Notice>,
        onFailure: () => <Notice tone="destructive">Admin access required.</Notice>,
        onSuccess: ({ value }) => (
          <div className="divide-y divide-border rounded-lg border border-border">
            {value.members.map((member) => {
              const isOwner = member.role === "owner";
              return (
                <div key={member.id} className="flex items-center gap-3 p-3">
                  <div className="min-w-0 flex-1">
                    <p className="truncate text-sm text-foreground">
                      {member.name ?? member.email}
                      {member.isCurrentUser ? " (you)" : ""}
                    </p>
                    <p className="truncate text-xs text-muted-foreground">{member.email}</p>
                  </div>
                  {isOwner || member.isCurrentUser ? (
                    <span className="rounded-md bg-muted px-2 py-1 text-xs text-muted-foreground">
                      {member.role}
                    </span>
                  ) : (
                    <>
                      <NativeSelect
                        className="text-xs"
                        value={member.role === "admin" ? "admin" : "member"}
                        disabled={busy}
                        onChange={(e) => changeRole(member.id, e.target.value)}
                      >
                        {ROLES.map((role) => (
                          <NativeSelectOption key={role} value={role}>
                            {role}
                          </NativeSelectOption>
                        ))}
                      </NativeSelect>
                      <Button
                        variant="ghost"
                        size="sm"
                        disabled={busy}
                        onClick={() => remove(member.id, member.name ?? member.email)}
                      >
                        Remove
                      </Button>
                    </>
                  )}
                </div>
              );
            })}
          </div>
        ),
      })}
    </section>
  );
}

function InvitesSection() {
  const result = useAtomValue(invitesAtom);
  const [createState, doCreate] = useAtom(createInvite, { mode: "promiseExit" });
  const [, doRevoke] = useAtom(revokeInvite, { mode: "promiseExit" });
  const [role, setRole] = useState<string>("member");
  const [label, setLabel] = useState("");
  const creating = AsyncResult.isWaiting(createState);

  const create = async () => {
    const exit = await doCreate({
      payload: { role, label: label.trim() || undefined },
      reactivityKeys: inviteWriteKeys,
    });
    if (Exit.isSuccess(exit)) {
      setLabel("");
      setRole("member");
      toast.success("Invite link created");
      return;
    }
    toast.error("Failed to create invite");
  };

  const revoke = async (inviteId: string) => {
    const exit = await doRevoke({ params: { inviteId }, reactivityKeys: inviteWriteKeys });
    toast[Exit.isSuccess(exit) ? "success" : "error"](
      Exit.isSuccess(exit) ? "Invite revoked" : "Failed to revoke invite",
    );
  };

  return (
    <section className="space-y-3">
      <h2 className="text-sm font-medium text-foreground">Invite links</h2>
      <div className="flex flex-wrap items-end gap-3 rounded-lg border border-border p-3">
        <div className="space-y-1.5">
          <Label htmlFor="invite-label">Label (optional)</Label>
          <Input
            id="invite-label"
            placeholder="e.g. Alex"
            value={label}
            onChange={(e) => setLabel(e.target.value)}
          />
        </div>
        <div className="space-y-1.5">
          <Label htmlFor="invite-role">Role</Label>
          <NativeSelect id="invite-role" value={role} onChange={(e) => setRole(e.target.value)}>
            {ROLES.map((r) => (
              <NativeSelectOption key={r} value={r}>
                {r}
              </NativeSelectOption>
            ))}
          </NativeSelect>
        </div>
        <Button onClick={create} disabled={creating}>
          {creating ? "Creating…" : "Create invite"}
        </Button>
      </div>

      {AsyncResult.match(result, {
        onInitial: () => <Notice>Loading invites…</Notice>,
        onFailure: () => <Notice tone="destructive">Admin access required.</Notice>,
        onSuccess: ({ value }) => {
          const pending = value.invites.filter((i) => !i.usedAt);
          const used = value.invites.filter((i) => i.usedAt);
          return (
            <div className="space-y-4">
              {pending.length > 0 && (
                <div className="divide-y divide-border rounded-lg border border-border">
                  {pending.map((invite) => (
                    <div key={invite.id} className="flex items-center gap-3 p-3 text-sm">
                      <span className="font-mono text-xs">{invite.code}</span>
                      <span className="flex-1 truncate text-muted-foreground">
                        {invite.label ? `${invite.label} · ` : ""}
                        {invite.role}
                      </span>
                      <CopyButton value={`${window.location.origin}/join/${invite.code}`} />
                      <Button variant="ghost" size="sm" onClick={() => revoke(invite.id)}>
                        Revoke
                      </Button>
                    </div>
                  ))}
                </div>
              )}
              {used.length > 0 && (
                <div className="space-y-1">
                  <p className="text-xs font-medium text-muted-foreground">Redeemed</p>
                  <div className="divide-y divide-border rounded-lg border border-border">
                    {used.map((invite) => (
                      <div key={invite.id} className="flex items-center gap-3 p-3 text-sm">
                        <span className="font-mono text-xs text-muted-foreground">
                          {invite.code}
                        </span>
                        <span className="flex-1 truncate text-muted-foreground">
                          {invite.label ? `${invite.label} · ` : ""}
                          used by {invite.usedByEmail}
                        </span>
                      </div>
                    ))}
                  </div>
                </div>
              )}
              {pending.length === 0 && used.length === 0 && (
                <Notice>No invite links yet — create one to add someone.</Notice>
              )}
            </div>
          );
        },
      })}
    </section>
  );
}

function Notice({ children, tone }: { children: React.ReactNode; tone?: "destructive" }) {
  return (
    <div
      className={`rounded-md border border-border bg-card p-4 text-sm ${
        tone === "destructive" ? "text-destructive" : "text-muted-foreground"
      }`}
    >
      {children}
    </div>
  );
}
