import { loadDevicePrivateKey } from '@chat-app/shared/auth';
import {
  type DecryptedMessage,
  editEncryptedMessage,
  parseMessagePayload,
  softDeleteMessage,
} from '@chat-app/shared/chat';
import { extractErrorCode } from '@chat-app/shared/i18n';
import { useCallback, useEffect, useRef, useState } from 'react';
import { useTranslation } from 'react-i18next';

import { useAuth } from '../context/AuthContext';
import { devLocalSecretStore } from '../lib/secretStore';
import { supabase } from '../lib/supabase';
import type { AggregatedReaction } from '../lib/useMessageReactions';
import { AttachmentImage } from './AttachmentImage';
import { PencilIcon, PhoneIcon, PhoneOffIcon, SmileIcon, SpinnerIcon, TrashIcon, XIcon } from './icons';

const EMOJI_CHOICES = ['👍', '❤️', '😂', '🎉', '🔥', '😮', '😢', '🙏'];
const EDIT_WINDOW_MS = 24 * 60 * 60 * 1000;

interface Props {
  message: DecryptedMessage;
  mine: boolean;
  groupedWithPrev: boolean;
  conversationId: string;
  reactions: AggregatedReaction[];
  onToggleReaction: (emoji: string) => Promise<void>;
  showSeen?: boolean;
}

export function MessageBubble({
  message,
  mine,
  groupedWithPrev,
  conversationId,
  reactions,
  onToggleReaction,
  showSeen = false,
}: Props) {
  const { t } = useTranslation(['app']);
  const { session, device } = useAuth();

  const parsed = parseMessagePayload(message.plaintext);
  const initialText = parsed.kind === 'text' ? parsed.text : '';
  const initialAttachments = parsed.kind === 'text' ? parsed.attachments : [];

  const [editing, setEditing] = useState(false);
  const [editText, setEditText] = useState(initialText);
  const [busy, setBusy] = useState(false);
  const [editError, setEditError] = useState<string | null>(null);
  const [pickerOpen, setPickerOpen] = useState(false);
  const pickerRef = useRef<HTMLDivElement>(null);

  const createdAt = new Date(message.createdAt);
  const age = Date.now() - createdAt.getTime();
  const withinEditWindow = age < EDIT_WINDOW_MS;
  const bodyText = initialText;
  const attachments = initialAttachments;
  const canEdit =
    parsed.kind === 'text' &&
    mine &&
    withinEditWindow &&
    !message.deletedAt &&
    attachments.length === 0;
  const canDelete = parsed.kind === 'text' && mine && !message.deletedAt;

  const time = new Intl.DateTimeFormat(undefined, { hour: '2-digit', minute: '2-digit' }).format(
    createdAt,
  );

  useEffect(() => {
    if (!pickerOpen) return;
    function onClickOutside(e: MouseEvent) {
      if (pickerRef.current && !pickerRef.current.contains(e.target as Node)) {
        setPickerOpen(false);
      }
    }
    document.addEventListener('mousedown', onClickOutside);
    return () => document.removeEventListener('mousedown', onClickOutside);
  }, [pickerOpen]);

  const handleEditSave = useCallback(async () => {
    if (!session || !device) return;
    const trimmed = editText.trim();
    if (!trimmed || trimmed === bodyText) {
      setEditing(false);
      setEditError(null);
      return;
    }
    setBusy(true);
    setEditError(null);
    try {
      const priv = await loadDevicePrivateKey(devLocalSecretStore, session.user.id, device.id);
      if (!priv) throw new Error('private key not loaded');
      await editEncryptedMessage({
        client: supabase,
        messageId: message.id,
        conversationId,
        newPlaintext: trimmed,
        senderUserId: session.user.id,
        senderDeviceId: device.id,
        senderPrivateKey: priv,
      });
      setEditing(false);
    } catch (err: unknown) {
      const code = extractErrorCode(err);
      setEditError(
        code
          ? t('errors:' + code, { defaultValue: t('errors:generic') })
          : err instanceof Error
            ? err.message
            : t('errors:generic'),
      );
    } finally {
      setBusy(false);
    }
  }, [editText, message.id, message.plaintext, conversationId, session, device, t]);

  const handleDelete = useCallback(async () => {
    if (busy) return;
    setBusy(true);
    try {
      await softDeleteMessage(supabase, message.id);
    } catch (err: unknown) {
      console.error('delete failed', err);
    } finally {
      setBusy(false);
    }
  }, [busy, message.id]);

  const handlePickEmoji = useCallback(
    async (emoji: string) => {
      setPickerOpen(false);
      try {
        await onToggleReaction(emoji);
      } catch (err: unknown) {
        console.error('toggleReaction failed', err);
      }
    },
    [onToggleReaction],
  );

  if (message.deletedAt) {
    return (
      <div className={'flex ' + (mine ? 'justify-end' : 'justify-start')}>
        <div className="max-w-[70%] rounded-2xl border border-white/5 bg-white/5 px-3.5 py-1.5 text-xs italic text-neutral-500">
          {t('app:chats.deleted')}
        </div>
      </div>
    );
  }

  if (parsed.kind === 'call_event') {
    return <CallEventRow parsed={parsed} mine={mine} time={time} />;
  }

  return (
    <div className={'flex ' + (mine ? 'justify-end' : 'justify-start')}>
      <div
        className={
          'group relative max-w-[70%] ' + (groupedWithPrev ? 'mt-0.5' : 'mt-2')
        }
      >
        {editing ? (
          <div className="rounded-2xl border border-brand-400/40 bg-ink-900/80 p-2 backdrop-blur-xl">
            <textarea
              value={editText}
              onChange={(e) => setEditText(e.target.value)}
              onKeyDown={(e) => {
                if (e.key === 'Enter' && !e.shiftKey) {
                  e.preventDefault();
                  void handleEditSave();
                }
                if (e.key === 'Escape') {
                  setEditing(false);
                  setEditError(null);
                }
              }}
              rows={2}
              autoFocus
              className="w-full resize-none rounded-md bg-ink-800 px-3 py-2 text-sm text-white outline-none focus:ring-2 focus:ring-brand-400/60"
            />
            {editError && (
              <p className="mt-1.5 break-words rounded-md border border-rose-500/20 bg-rose-500/10 px-2 py-1 text-[11px] text-rose-200">
                {editError}
              </p>
            )}
            <div className="mt-1.5 flex justify-end gap-2">
              <button
                type="button"
                onClick={() => {
                  setEditing(false);
                  setEditError(null);
                }}
                className="cursor-pointer rounded-md border border-white/10 bg-white/5 px-3 py-1 text-xs text-neutral-200 hover:bg-white/10"
              >
                {t('app:friends.action_cancel')}
              </button>
              <button
                type="button"
                disabled={busy}
                onClick={() => void handleEditSave()}
                className="inline-flex cursor-pointer items-center gap-1.5 rounded-md bg-brand-500/90 px-3 py-1 text-xs font-semibold text-white hover:bg-brand-400 disabled:opacity-60"
              >
                {busy && <SpinnerIcon className="h-3.5 w-3.5" />}
                <span>{t('common:save', { defaultValue: 'Save' })}</span>
              </button>
            </div>
          </div>
        ) : (
          <div
            className={
              'break-words rounded-2xl px-3.5 py-2 text-sm ' +
              (mine
                ? 'bg-brand-500/85 text-white'
                : 'border border-white/5 bg-ink-900/70 text-neutral-100')
            }
          >
            {message.plaintext === null ? (
              <span className="italic text-neutral-400">…cannot decrypt</span>
            ) : (
              <>
                {bodyText.length > 0 && <div>{bodyText}</div>}
                {attachments.map((a) => (
                  <AttachmentImage key={a.id} handle={a} />
                ))}
              </>
            )}
            <div
              className={
                'mt-1 flex items-center gap-1.5 text-[10px] ' +
                (mine ? 'text-brand-100/70' : 'text-neutral-500')
              }
            >
              <span>{time}</span>
              {message.editedAt && !message.deletedAt && (
                <span className="italic">· {t('app:chats.edited')}</span>
              )}
            </div>
          </div>
        )}

        {showSeen && mine && !editing && !message.deletedAt && (
          <p className="mt-0.5 text-right text-[10px] text-neutral-500">
            {t('app:chats.seen')}
          </p>
        )}

        {reactions.length > 0 && !editing && (
          <div className={'mt-1 flex flex-wrap gap-1 ' + (mine ? 'justify-end' : 'justify-start')}>
            {reactions.map((r) => (
              <button
                key={r.emoji}
                type="button"
                onClick={() => void onToggleReaction(r.emoji)}
                className={
                  'inline-flex cursor-pointer items-center gap-1 rounded-full border px-2 py-0.5 text-xs transition focus:outline-none focus-visible:ring-2 focus-visible:ring-brand-400/40 ' +
                  (r.mine
                    ? 'border-brand-400/40 bg-brand-500/20 text-brand-100'
                    : 'border-white/10 bg-white/5 text-neutral-200 hover:bg-white/10')
                }
              >
                <span>{r.emoji}</span>
                <span className="text-[10px] font-medium">{r.count}</span>
              </button>
            ))}
          </div>
        )}

        {!editing && (
          <div
            className={
              'pointer-events-none absolute top-0 z-20 opacity-0 transition group-hover:pointer-events-auto group-hover:opacity-100 ' +
              (mine ? 'right-full pr-2' : 'left-full pl-2')
            }
          >
            <div className="flex items-center gap-0.5 rounded-lg border border-white/10 bg-ink-900/90 p-1 shadow-lg backdrop-blur-xl">
              <ActionButton
                label={t('app:friends.action_accept', { defaultValue: 'React' })}
                onClick={() => setPickerOpen((v) => !v)}
                icon={<SmileIcon className="h-4 w-4" />}
              />
              {canEdit && (
                <ActionButton
                  label="Edit"
                  onClick={() => {
                    setEditText(message.plaintext ?? '');
                    setEditing(true);
                  }}
                  icon={<PencilIcon className="h-4 w-4" />}
                />
              )}
              {canDelete && (
                <ActionButton
                  label="Delete"
                  onClick={() => void handleDelete()}
                  icon={<TrashIcon className="h-4 w-4" />}
                  tone="danger"
                />
              )}
            </div>

            {pickerOpen && (
              <div
                ref={pickerRef}
                role="menu"
                className={
                  'absolute z-30 mt-1 flex gap-1 rounded-lg border border-white/10 bg-ink-900/95 p-1.5 shadow-xl backdrop-blur-xl ' +
                  (mine ? 'right-0' : 'left-0')
                }
              >
                {EMOJI_CHOICES.map((e) => (
                  <button
                    key={e}
                    type="button"
                    onClick={() => void handlePickEmoji(e)}
                    className="cursor-pointer rounded-md px-2 py-1 text-lg transition hover:bg-white/10 focus:outline-none focus-visible:ring-2 focus-visible:ring-brand-400/40"
                  >
                    {e}
                  </button>
                ))}
                <button
                  type="button"
                  onClick={() => setPickerOpen(false)}
                  className="cursor-pointer rounded-md px-1.5 py-1 text-neutral-500 transition hover:bg-white/10"
                >
                  <XIcon className="h-4 w-4" />
                </button>
              </div>
            )}
          </div>
        )}
      </div>
    </div>
  );
}

function CallEventRow({
  parsed,
  mine,
  time,
}: {
  parsed: { status: string; mediaKind: string; durationSec: number };
  mine: boolean;
  time: string;
}) {
  const { t } = useTranslation(['app']);
  const status = parsed.status;
  const isMissed = status === 'missed' || status === 'declined';

  const Icon = isMissed ? PhoneOffIcon : PhoneIcon;
  const tone = isMissed
    ? 'border-rose-500/20 bg-rose-500/10 text-rose-200'
    : 'border-emerald-500/20 bg-emerald-500/10 text-emerald-200';

  const label =
    status === 'ended'
      ? mine
        ? t('app:chats.call_outgoing', { defaultValue: 'Outgoing call' })
        : t('app:chats.call_incoming', { defaultValue: 'Incoming call' })
      : status === 'missed'
        ? mine
          ? t('app:chats.call_no_answer', { defaultValue: 'No answer' })
          : t('app:chats.call_missed', { defaultValue: 'Missed call' })
        : t('app:chats.call_declined', { defaultValue: 'Call declined' });

  const duration = parsed.durationSec > 0 ? formatDuration(parsed.durationSec) : null;

  return (
    <div className="my-2 flex justify-center">
      <div
        className={
          'inline-flex items-center gap-2 rounded-full border px-3 py-1 text-xs font-medium ' + tone
        }
      >
        <Icon className="h-3.5 w-3.5" />
        <span>{label}</span>
        {duration && <span className="font-mono text-[11px] opacity-80">· {duration}</span>}
        <span className="text-[10px] opacity-60">· {time}</span>
      </div>
    </div>
  );
}

function formatDuration(totalSec: number): string {
  const m = Math.floor(totalSec / 60);
  const s = totalSec % 60;
  if (m === 0) return s + 's';
  return m + ':' + s.toString().padStart(2, '0');
}

function ActionButton({
  label,
  onClick,
  icon,
  tone,
}: {
  label: string;
  onClick: () => void;
  icon: React.ReactNode;
  tone?: 'danger';
}) {
  return (
    <button
      type="button"
      aria-label={label}
      title={label}
      onClick={onClick}
      className={
        'flex h-7 w-7 cursor-pointer items-center justify-center rounded-md transition focus:outline-none focus-visible:ring-2 focus-visible:ring-brand-400/40 ' +
        (tone === 'danger'
          ? 'text-neutral-400 hover:bg-rose-500/20 hover:text-rose-200'
          : 'text-neutral-400 hover:bg-white/10 hover:text-neutral-100')
      }
    >
      {icon}
    </button>
  );
}
