import { useEffect, useRef, useState } from "react";
import { useTranslation } from "react-i18next";
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import { ArrowLeft, Lock, Send, ShieldCheck, SquarePen } from "lucide-react";
import { api, type Conversation, type Message, type MessageUser, type MyKeyBundle } from "../lib/api";
import { useLiveQuery } from "../lib/useLiveQuery";
import { relativeTime } from "../lib/format";
import { onMessage } from "../lib/messagesSocket";
import * as e2ee from "../lib/e2ee";
import Avatar from "./Avatar";

const POLL_MS = 20000; // safety-net poll; live updates come over the WebSocket
const SYSTEM_ID = 0;

// Set-once cache of partner public keys (the server is the directory).
const pubCache = new Map<number, string>();
async function partnerPub(partnerId: number): Promise<string> {
  const cached = pubCache.get(partnerId);
  if (cached) return cached;
  const r = await api.messagePublicKey(partnerId);
  pubCache.set(partnerId, r.public_key);
  return r.public_key;
}

type View = "list" | "new" | { partnerId: number; name?: string; avatar?: string | null; system?: boolean };

export default function Messages({ meId }: { meId: number }) {
  const qc = useQueryClient();
  const [view, setView] = useState<View>("list");
  const [unlocked, setUnlocked] = useState(e2ee.isUnlocked());

  const keyQ = useQuery({ queryKey: ["message-key"], queryFn: api.messageMyKey, staleTime: 60_000 });
  const configured = keyQ.data?.configured ?? false;

  // Restore this device's unlocked key (set once per device, then survives reloads).
  useEffect(() => {
    e2ee.loadFromDevice(meId).then((ok) => ok && setUnlocked(true));
  }, [meId]);

  // Live delivery: a pushed message refreshes the conversation list, unread badge, and the
  // affected thread (which re-decrypts). Near-instant; the poll is only a fallback.
  useEffect(
    () =>
      onMessage((m) => {
        qc.invalidateQueries({ queryKey: ["conversations"] });
        qc.invalidateQueries({ queryKey: ["message-unread"] });
        const partner = m.kind === "system" ? SYSTEM_ID : m.sender_id === meId ? m.recipient_id : m.sender_id;
        qc.invalidateQueries({ queryKey: ["thread", partner] });
      }),
    [qc, meId]
  );

  const gate = !unlocked ? (
    <KeyGate
      meId={meId}
      configured={configured}
      bundle={keyQ.data}
      onUnlocked={() => {
        setUnlocked(true);
        qc.invalidateQueries({ queryKey: ["message-key"] });
      }}
    />
  ) : null;

  if (typeof view === "object") {
    return (
      <Thread
        meId={meId}
        partnerId={view.partnerId}
        isSystem={!!view.system}
        seedName={view.name}
        seedAvatar={view.avatar}
        unlocked={unlocked}
        gate={gate}
        onBack={() => setView("list")}
      />
    );
  }
  if (view === "new") {
    return <Directory onPick={(u) => setView({ partnerId: u.id, name: u.name, avatar: u.avatar_url })} onBack={() => setView("list")} />;
  }
  return (
    <ConversationList
      unlocked={unlocked}
      gate={gate}
      onOpen={(c) =>
        setView({ partnerId: c.partner.id, name: c.partner.name, avatar: c.partner.avatar_url, system: c.partner.id === SYSTEM_ID })
      }
      onNew={() => setView("new")}
    />
  );
}

function KeyGate({
  meId,
  configured,
  bundle,
  onUnlocked,
}: {
  meId: number;
  configured: boolean;
  bundle: MyKeyBundle | undefined;
  onUnlocked: () => void;
}) {
  const { t } = useTranslation();
  const [pass, setPass] = useState("");
  const [confirm, setConfirm] = useState("");
  const [err, setErr] = useState<string | null>(null);
  const [busy, setBusy] = useState(false);

  async function submit() {
    setErr(null);
    if (pass.length < 6) return setErr(t("messages.passTooShort"));
    if (!configured && pass !== confirm) return setErr(t("messages.passMismatch"));
    setBusy(true);
    try {
      if (configured) {
        await e2ee.unlock(meId, pass, bundle!);
      } else {
        const payload = await e2ee.setup(meId, pass);
        await api.setupMessageKey(payload);
      }
      onUnlocked();
    } catch {
      setErr(configured ? t("messages.wrongPass") : t("messages.setupFailed"));
    } finally {
      setBusy(false);
    }
  }

  return (
    <div className="glass rounded-2xl p-4 space-y-3">
      <div className="flex items-center gap-2">
        <ShieldCheck className="w-5 h-5 text-accent shrink-0" />
        <div className="font-semibold">{configured ? t("messages.unlockTitle") : t("messages.setupTitle")}</div>
      </div>
      <p className="text-sm text-muted">{configured ? t("messages.unlockBody") : t("messages.setupBody")}</p>
      <input
        type="password"
        value={pass}
        onChange={(e) => setPass(e.target.value)}
        onKeyDown={(e) => e.key === "Enter" && configured && submit()}
        placeholder={t("messages.passphrase")}
        className="w-full bg-card border border-border rounded-xl px-3 py-2 text-sm outline-none focus:border-accent"
      />
      {!configured && (
        <input
          type="password"
          value={confirm}
          onChange={(e) => setConfirm(e.target.value)}
          placeholder={t("messages.passphraseConfirm")}
          className="w-full bg-card border border-border rounded-xl px-3 py-2 text-sm outline-none focus:border-accent"
        />
      )}
      {err && <div className="text-sm text-red-400">{err}</div>}
      <button
        onClick={submit}
        disabled={busy || !pass}
        className="inline-flex items-center gap-2 px-4 py-2 rounded-xl font-semibold bg-accent text-accent-fg hover:opacity-90 transition disabled:opacity-40"
      >
        <Lock className="w-4 h-4" />
        {configured ? t("messages.unlock") : t("messages.setUp")}
      </button>
      {!configured && <p className="text-xs text-muted">{t("messages.setupWarn")}</p>}
    </div>
  );
}

function ConversationList({
  unlocked,
  gate,
  onOpen,
  onNew,
}: {
  unlocked: boolean;
  gate: React.ReactNode;
  onOpen: (c: Conversation) => void;
  onNew: () => void;
}) {
  const { t } = useTranslation();
  const q = useLiveQuery<{ items: Conversation[] }>(["conversations"], api.conversations, { intervalMs: POLL_MS });
  const items = q.data?.items ?? [];
  const [previews, setPreviews] = useState<Record<number, string>>({});

  // Decrypt the latest message of each user conversation for the list preview (system previews
  // are already plaintext). Runs whenever the conversations or unlock state change.
  useEffect(() => {
    if (!unlocked) return;
    let cancelled = false;
    (async () => {
      const out: Record<number, string> = {};
      for (const c of items) {
        const m = c.last_message;
        if (m.kind === "user" && m.ciphertext && m.iv) {
          try {
            const pub = await partnerPub(c.partner.id);
            out[c.partner.id] = await e2ee.decryptFrom(c.partner.id, pub, m.ciphertext, m.iv);
          } catch {
            out[c.partner.id] = "⚠";
          }
        }
      }
      if (!cancelled) setPreviews(out);
    })();
    return () => {
      cancelled = true;
    };
  }, [items, unlocked]);

  function previewText(c: Conversation): string {
    const m = c.last_message;
    if (m.kind === "system") return m.body ?? "";
    if (!unlocked) return "🔒 " + t("messages.encrypted");
    return previews[c.partner.id] ?? "…";
  }

  return (
    <div className="space-y-3">
      <div className="flex items-center justify-between">
        <div className="text-xs uppercase tracking-wide text-muted">{t("messages.conversations")}</div>
        {unlocked && (
          <button
            onClick={onNew}
            className="inline-flex items-center gap-1.5 text-xs px-2.5 py-1.5 rounded-lg bg-accent text-accent-fg hover:opacity-90 transition"
          >
            <SquarePen className="w-4 h-4" />
            {t("messages.new")}
          </button>
        )}
      </div>
      {gate}
      {q.isLoading && !q.data ? (
        <div className="p-8 text-muted text-center">{t("common.loading")}</div>
      ) : items.length === 0 ? (
        <div className="glass rounded-2xl p-10 text-center text-muted">{t("messages.empty")}</div>
      ) : (
        <div className="flex flex-col gap-1.5">
          {items.map((c) => (
            <button
              key={c.partner.id}
              onClick={() => onOpen(c)}
              className="flex items-center gap-3 p-2.5 rounded-xl text-left hover:bg-card transition"
            >
              <Avatar src={c.partner.avatar_url} fallback={c.partner.name} className="w-10 h-10 rounded-full shrink-0 text-sm" />
              <div className="min-w-0 flex-1">
                <div className="flex items-center gap-2">
                  <span className={`truncate ${c.unread > 0 ? "font-semibold" : "font-medium"}`}>{c.partner.name}</span>
                  <span className="ml-auto text-[11px] text-muted shrink-0">{relativeTime(c.last_message.created_at)}</span>
                </div>
                <div className="flex items-center gap-2">
                  <span className={`truncate text-sm ${c.unread > 0 ? "text-fg" : "text-muted"}`}>{previewText(c)}</span>
                  {c.unread > 0 && (
                    <span className="ml-auto shrink-0 min-w-5 h-5 px-1.5 grid place-items-center rounded-full bg-accent text-accent-fg text-[11px] font-semibold">
                      {c.unread}
                    </span>
                  )}
                </div>
              </div>
            </button>
          ))}
        </div>
      )}
    </div>
  );
}

function Directory({ onPick, onBack }: { onPick: (u: MessageUser) => void; onBack: () => void }) {
  const { t } = useTranslation();
  const [filter, setFilter] = useState("");
  const q = useQuery({ queryKey: ["message-users"], queryFn: api.messageUsers });
  const users = (q.data ?? []).filter((u) => u.name.toLowerCase().includes(filter.trim().toLowerCase()));
  return (
    <div className="space-y-3">
      <div className="flex items-center gap-2">
        <button onClick={onBack} className="p-1.5 rounded-lg text-muted hover:text-fg hover:bg-card transition">
          <ArrowLeft className="w-4 h-4" />
        </button>
        <div className="text-xs uppercase tracking-wide text-muted">{t("messages.newTitle")}</div>
      </div>
      <input
        value={filter}
        onChange={(e) => setFilter(e.target.value)}
        placeholder={t("messages.searchPeople")}
        className="w-full bg-card border border-border rounded-xl px-3 py-2 text-sm outline-none focus:border-accent"
      />
      {q.isLoading ? (
        <div className="p-8 text-muted text-center">{t("common.loading")}</div>
      ) : users.length === 0 ? (
        <div className="glass rounded-2xl p-10 text-center text-muted">{t("messages.noPeople")}</div>
      ) : (
        <div className="flex flex-col gap-1">
          {users.map((u) => (
            <button
              key={u.id}
              onClick={() => onPick(u)}
              className="flex items-center gap-3 p-2.5 rounded-xl text-left hover:bg-card transition"
            >
              <Avatar src={u.avatar_url} fallback={u.name} className="w-9 h-9 rounded-full shrink-0 text-sm" />
              <span className="truncate font-medium">{u.name}</span>
            </button>
          ))}
        </div>
      )}
    </div>
  );
}

function Thread({
  meId,
  partnerId,
  isSystem,
  seedName,
  seedAvatar,
  unlocked,
  gate,
  onBack,
}: {
  meId: number;
  partnerId: number;
  isSystem: boolean;
  seedName?: string;
  seedAvatar?: string | null;
  unlocked: boolean;
  gate: React.ReactNode;
  onBack: () => void;
}) {
  const { t } = useTranslation();
  const qc = useQueryClient();
  const [draft, setDraft] = useState("");
  const [plain, setPlain] = useState<Record<number, string>>({});
  const bottomRef = useRef<HTMLDivElement>(null);
  const needsKey = !isSystem && !unlocked;

  const q = useLiveQuery<{ partner: MessageUser; items: Message[] }>(
    ["thread", partnerId],
    () => api.thread(partnerId),
    { intervalMs: POLL_MS, enabled: !needsKey }
  );
  const partner = q.data?.partner;
  const items = q.data?.items ?? [];

  // Decrypt user-message bodies for display (system messages are already plaintext).
  useEffect(() => {
    if (isSystem || !unlocked) return;
    let cancelled = false;
    (async () => {
      const out: Record<number, string> = {};
      for (const m of items) {
        if (m.ciphertext && m.iv) {
          try {
            const pub = await partnerPub(partnerId);
            out[m.id] = await e2ee.decryptFrom(partnerId, pub, m.ciphertext, m.iv);
          } catch {
            out[m.id] = "⚠ " + t("messages.cantDecrypt");
          }
        }
      }
      if (!cancelled) setPlain(out);
    })();
    return () => {
      cancelled = true;
    };
  }, [items, unlocked, isSystem, partnerId, t]);

  useEffect(() => {
    if (q.dataUpdatedAt) {
      qc.invalidateQueries({ queryKey: ["conversations"] });
      qc.invalidateQueries({ queryKey: ["message-unread"] });
    }
  }, [q.dataUpdatedAt, qc]);

  useEffect(() => {
    bottomRef.current?.scrollIntoView({ block: "end" });
  }, [items.length, plain]);

  const send = useMutation({
    mutationFn: async (text: string) => {
      const pub = await partnerPub(partnerId);
      const { ciphertext, iv } = await e2ee.encryptFor(partnerId, pub, text);
      return api.sendMessage(partnerId, ciphertext, iv);
    },
    onSuccess: () => {
      setDraft("");
      qc.invalidateQueries({ queryKey: ["thread", partnerId] });
      qc.invalidateQueries({ queryKey: ["conversations"] });
    },
  });

  const submit = () => {
    const text = draft.trim();
    if (text && !send.isPending) send.mutate(text);
  };

  const name = partner?.name ?? seedName ?? "";
  return (
    <div className="flex flex-col h-[60vh] min-h-80">
      <div className="flex items-center gap-3 pb-3 border-b border-border">
        <button onClick={onBack} className="p-1.5 rounded-lg text-muted hover:text-fg hover:bg-card transition">
          <ArrowLeft className="w-4 h-4" />
        </button>
        <Avatar src={partner?.avatar_url ?? seedAvatar} fallback={name} className="w-9 h-9 rounded-full shrink-0 text-sm" />
        <span className="font-semibold truncate">{name}</span>
        {!isSystem && <ShieldCheck className="w-4 h-4 text-muted shrink-0" aria-label={t("messages.encryptedHint")} />}
      </div>

      {needsKey ? (
        <div className="flex-1 grid place-items-center py-6">
          <div className="w-full max-w-sm">{gate}</div>
        </div>
      ) : (
        <>
          <div className="flex-1 overflow-y-auto py-3 flex flex-col gap-2">
            {items.length === 0 ? (
              <div className="m-auto text-sm text-muted">{t("messages.threadEmpty")}</div>
            ) : (
              items.map((m) => {
                const mine = m.sender_id === meId;
                const text = m.kind === "system" ? m.body ?? "" : plain[m.id] ?? "…";
                return (
                  <div key={m.id} className={`flex ${mine ? "justify-end" : "justify-start"}`}>
                    <div
                      className={`max-w-[78%] rounded-2xl px-3 py-2 text-sm whitespace-pre-wrap break-words ${
                        mine ? "bg-accent text-accent-fg rounded-br-sm" : "bg-card rounded-bl-sm"
                      }`}
                    >
                      {text}
                      <div className={`text-[10px] mt-1 ${mine ? "text-accent-fg/70" : "text-muted"}`}>
                        {relativeTime(m.created_at)}
                      </div>
                    </div>
                  </div>
                );
              })
            )}
            <div ref={bottomRef} />
          </div>

          {isSystem ? (
            <div className="pt-3 border-t border-border text-center text-xs text-muted">{t("messages.systemReadOnly")}</div>
          ) : (
            <div className="flex items-end gap-2 pt-3 border-t border-border">
              <textarea
                value={draft}
                onChange={(e) => setDraft(e.target.value)}
                onKeyDown={(e) => {
                  if (e.key === "Enter" && !e.shiftKey) {
                    e.preventDefault();
                    submit();
                  }
                }}
                rows={1}
                placeholder={t("messages.writePlaceholder")}
                className="flex-1 resize-none max-h-32 bg-card border border-border rounded-xl px-3 py-2 text-sm outline-none focus:border-accent"
              />
              <button
                onClick={submit}
                disabled={!draft.trim() || send.isPending}
                className="shrink-0 p-2.5 rounded-xl bg-accent text-accent-fg hover:opacity-90 transition disabled:opacity-40"
                aria-label={t("messages.send")}
                title={t("messages.send")}
              >
                <Send className="w-4 h-4" />
              </button>
            </div>
          )}
        </>
      )}
    </div>
  );
}
