import { loadDevicePrivateKey } from '@chat-app/shared/auth';
import {
  type AttachmentHandle,
  type DecryptedMessage,
  downloadAndDecryptAttachment,
  encryptAndUploadAttachment,
  insertAttachmentRow,
  parseMessagePayload,
  sendEncryptedMessage,
} from '@chat-app/shared/chat';
import { extractErrorCode } from '@chat-app/shared/i18n';
import { bytesToPgHex } from '@chat-app/shared/supabase';
import { useEffect, useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next';

import { useAuth } from '../context/AuthContext';
import { useConversationsContext } from '../context/ConversationsContext';
import { devLocalSecretStore } from '../lib/secretStore';
import { supabase } from '../lib/supabase';
import { Avatar } from './Avatar';
import { ForwardIcon, SpinnerIcon, UsersIcon, XIcon } from './icons';

interface Props {
  open: boolean;
  message: DecryptedMessage | null;
  currentConversationId: string | null;
  onClose: () => void;
}

// Forwards a message's plaintext to one or more conversations. Attachments are
// NOT carried over yet (would require re-uploading + re-encrypting under the
// new conversation key); only the text payload is forwarded for now and the
// preview hints at the dropped attachment.
export function ForwardDialog({ open, message, currentConversationId, onClose }: Props) {
  const { t } = useTranslation(['app', 'errors']);
  const { session, device } = useAuth();
  const { conversations } = useConversationsContext();

  const [selected, setSelected] = useState<Set<string>>(new Set());
  const [busy, setBusy] = useState(false);
  const [error, setError] = useState<string | null>(null);
  const [done, setDone] = useState(false);

  useEffect(() => {
    if (!open) return;
    setSelected(new Set());
    setError(null);
    setDone(false);
  }, [open, message?.id]);

  const targets = useMemo(() => {
    return conversations
      .filter((c) => c.id !== currentConversationId && c.acceptedByMe)
      .sort((a, b) => {
        const ta = a.lastMessageAt ?? a.createdAt;
        const tb = b.lastMessageAt ?? b.createdAt;
        return tb.localeCompare(ta);
      });
  }, [conversations, currentConversationId]);

  const preview = useMemo(() => {
    if (!message?.plaintext) return '';
    const p = parseMessagePayload(message.plaintext);
    if (p.kind === 'poll') return 'Umfrage: ' + p.question;
    if (p.kind !== 'text') return '';
    return p.text.length > 140 ? p.text.slice(0, 140) + '…' : p.text;
  }, [message]);

  const sourceAttachments = useMemo<AttachmentHandle[]>(() => {
    if (!message?.plaintext) return [];
    const p = parseMessagePayload(message.plaintext);
    return p.kind === 'text' ? p.attachments : [];
  }, [message]);

  if (!open || !message) return null;

  function toggle(id: string) {
    setSelected((prev) => {
      const next = new Set(prev);
      if (next.has(id)) next.delete(id);
      else next.add(id);
      return next;
    });
  }

  async function handleSend() {
    if (!session?.user.id || !device?.id || !message) return;
    if (selected.size === 0) return;
    setBusy(true);
    setError(null);
    try {
      const priv = await loadDevicePrivateKey(devLocalSecretStore, session.user.id, device.id);
      if (!priv) throw new Error('private key not loaded');

      const hasAttachments = sourceAttachments.length > 0;
      const text = preview || (hasAttachments ? '' : '');
      if (!text && !hasAttachments) throw new Error('nothing to forward');

      // Download+decrypt source attachments ONCE (same plaintext goes to every
      // target). For each target conv we re-encrypt under fresh per-attachment
      // keys and re-upload under the target conv's storage folder — source and
      // target conv-keys differ, so the bytes must actually move.
      const decryptedBlobs: { mime: string; size: number; width?: number; height?: number; blob: Blob }[] =
        [];
      for (const h of sourceAttachments) {
        const blob = await downloadAndDecryptAttachment({ client: supabase, handle: h });
        const entry: {
          mime: string;
          size: number;
          width?: number;
          height?: number;
          blob: Blob;
        } = { mime: h.mimeType, size: h.sizeBytes, blob };
        if (h.width !== undefined) entry.width = h.width;
        if (h.height !== undefined) entry.height = h.height;
        decryptedBlobs.push(entry);
      }

      for (const convId of selected) {
        const newHandles: AttachmentHandle[] = [];
        const blobNonceHex = new Map<string, string>();
        for (const src of decryptedBlobs) {
          const res = await encryptAndUploadAttachment({
            client: supabase,
            conversationId: convId,
            file: src.blob,
            mimeType: src.mime,
            sizeBytes: src.size,
            ...(src.width !== undefined ? { width: src.width } : {}),
            ...(src.height !== undefined ? { height: src.height } : {}),
          });
          newHandles.push(res.handle);
          blobNonceHex.set(res.handle.id, bytesToPgHex(res.nonce));
        }

        const msg = await sendEncryptedMessage({
          client: supabase,
          conversationId: convId,
          plaintext: text,
          senderUserId: session.user.id,
          senderDeviceId: device.id,
          senderPrivateKey: priv,
          ...(newHandles.length > 0 ? { attachmentHandles: newHandles } : {}),
        });

        for (const h of newHandles) {
          const bn = blobNonceHex.get(h.id) ?? '\\x';
          await insertAttachmentRow(supabase, msg.id, h, bn);
        }
      }
      setDone(true);
      window.setTimeout(onClose, 700);
    } catch (err: unknown) {
      const code = extractErrorCode(err);
      setError(
        code
          ? t('errors:' + code, { defaultValue: t('errors:generic') })
          : err instanceof Error
            ? err.message
            : t('errors:generic'),
      );
    } finally {
      setBusy(false);
    }
  }

  return (
    <div
      role="dialog"
      aria-modal="true"
      aria-label={t('app:chats.forward', { defaultValue: 'Weiterleiten' })}
      className="fixed inset-0 z-50 flex items-center justify-center bg-black/50 p-4"
      onClick={onClose}
    >
      <div
        onClick={(e) => e.stopPropagation()}
        className="flex max-h-[80vh] w-full max-w-md flex-col overflow-hidden rounded-2xl border border-line bg-surface-3 shadow-xl"
      >
        <header className="flex items-center justify-between border-b border-line px-5 py-3">
          <div className="flex items-center gap-2">
            <ForwardIcon className="h-4 w-4 text-accent" />
            <h3 className="font-display text-sm font-semibold text-fg">
              {t('app:chats.forward', { defaultValue: 'Weiterleiten' })}
            </h3>
          </div>
          <button
            type="button"
            onClick={onClose}
            aria-label="Close"
            className="flex h-8 w-8 cursor-pointer items-center justify-center rounded-lg text-fg-muted transition hover:bg-surface-2 hover:text-fg"
          >
            <XIcon className="h-4 w-4" />
          </button>
        </header>

        <div className="border-b border-line bg-surface-2 px-5 py-3">
          <p className="text-[10px] font-semibold uppercase tracking-wider text-fg-muted">
            {t('app:chats.forward_preview', { defaultValue: 'Vorschau' })}
          </p>
          <p className="mt-1 line-clamp-3 break-words text-sm text-fg">
            {preview || (sourceAttachments.length > 0 ? '📎' : '…')}
          </p>
          {sourceAttachments.length > 0 && (
            <p className="mt-1 text-[11px] text-fg-muted">
              📎{' '}
              {t('app:chats.forward_attachments_count', {
                count: sourceAttachments.length,
                defaultValue: '{{count}} Anhang wird mit weitergeleitet',
              })}
            </p>
          )}
        </div>

        <div className="flex-1 overflow-y-auto p-2">
          {targets.length === 0 ? (
            <p className="px-4 py-6 text-center text-sm text-fg-muted">
              {t('app:chats.forward_no_targets', {
                defaultValue: 'Keine anderen Unterhaltungen verfügbar.',
              })}
            </p>
          ) : (
            <ul className="space-y-0.5">
              {targets.map((c) => {
                const isGroup = c.type === 'group';
                const title = isGroup ? c.name ?? '?' : c.peer?.displayName ?? '?';
                const avatarUrl = isGroup ? c.avatarUrl ?? null : c.peer?.avatarUrl ?? null;
                const checked = selected.has(c.id);
                return (
                  <li key={c.id}>
                    <button
                      type="button"
                      onClick={() => toggle(c.id)}
                      className={
                        'flex w-full cursor-pointer items-center gap-3 rounded-lg px-3 py-2 text-left transition focus:outline-none focus-visible:ring-2 focus-visible:ring-accent/40 ' +
                        (checked ? 'bg-accent/15' : 'hover:bg-surface-2')
                      }
                    >
                      {avatarUrl ? (
                        <Avatar
                          url={avatarUrl}
                          displayName={title}
                          className="h-9 w-9 text-sm"
                        />
                      ) : isGroup ? (
                        <div className="flex h-9 w-9 shrink-0 items-center justify-center rounded-full bg-accent/20 text-accent">
                          <UsersIcon className="h-4 w-4" />
                        </div>
                      ) : (
                        <Avatar
                          url={null}
                          displayName={title}
                          className="h-9 w-9 text-sm"
                        />
                      )}
                      <span className="min-w-0 flex-1 truncate text-sm font-medium text-fg">
                        {title}
                      </span>
                      <span
                        aria-hidden="true"
                        className={
                          'flex h-5 w-5 shrink-0 items-center justify-center rounded border ' +
                          (checked ? 'border-accent bg-accent text-accent-fg' : 'border-line bg-surface-2')
                        }
                      >
                        {checked && (
                          <svg viewBox="0 0 24 24" className="h-3.5 w-3.5" fill="none" stroke="currentColor" strokeWidth="3" strokeLinecap="round" strokeLinejoin="round">
                            <polyline points="20 6 9 17 4 12" />
                          </svg>
                        )}
                      </span>
                    </button>
                  </li>
                );
              })}
            </ul>
          )}
        </div>

        {error && (
          <p
            role="alert"
            className="border-t border-rose-500/30 bg-rose-500/10 px-5 py-2 text-xs text-rose-700 dark:text-rose-200"
          >
            {error}
          </p>
        )}

        <footer className="flex items-center justify-end gap-2 border-t border-line bg-surface-2 px-5 py-3">
          <button
            type="button"
            onClick={onClose}
            className="cursor-pointer rounded-md border border-line bg-surface-3 px-3 py-2 text-sm text-fg transition hover:bg-surface-2"
          >
            {t('app:friends.action_cancel', { defaultValue: 'Abbrechen' })}
          </button>
          <button
            type="button"
            disabled={busy || selected.size === 0 || done}
            onClick={() => void handleSend()}
            className="inline-flex cursor-pointer items-center gap-1.5 rounded-md bg-accent px-3 py-2 text-sm font-semibold text-accent-fg transition hover:brightness-110 disabled:cursor-not-allowed disabled:opacity-60"
          >
            {busy && <SpinnerIcon className="h-4 w-4" />}
            <span>
              {done
                ? t('app:chats.forward_done', { defaultValue: 'Gesendet' })
                : t('app:chats.forward_send', {
                    count: selected.size,
                    defaultValue: 'An {{count}} senden',
                  })}
            </span>
          </button>
        </footer>
      </div>
    </div>
  );
}
