import { render, screen, waitFor, fireEvent } from '@testing-library/react';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { vi, describe, it, expect, beforeEach } from 'vitest';
import { useAuthStore } from '@/stores/auth';
import type { AuthUser } from '@alqove/types';
import { MessageThread } from '../message-thread';

const listMock = vi.fn();
const postMock = vi.fn();
const deleteMock = vi.fn();
const uploadMock = vi.fn();
vi.mock('@/lib/api', () => ({
  api: {
    messages: {
      list: (...a: unknown[]) => listMock(...a),
      post: (...a: unknown[]) => postMock(...a),
      delete: (...a: unknown[]) => deleteMock(...a),
      uploadAttachment: (...a: unknown[]) => uploadMock(...a),
    },
  },
}));

function wrap(node: React.ReactNode) {
  const qc = new QueryClient({ defaultOptions: { queries: { retry: false } } });
  return <QueryClientProvider client={qc}>{node}</QueryClientProvider>;
}

const buyerView = {
  orderId: 'o1',
  counterpartyName: 'Revive Boutique',
  viewerRole: 'buyer' as const,
  viewerUserId: 'buyer-1',
};

const messages = [
  {
    id: 'm1',
    thread_id: 't1',
    author_user_id: 'buyer-1',
    author_role: 'buyer' as const,
    body: 'Hi.',
    attachments: [],
    created_at: '2026-05-06T10:00:00Z',
    deleted_at: null,
    deleted_by_user_id: null,
    deleted_by_admin: false,
  },
  {
    id: 'm2',
    thread_id: 't1',
    author_user_id: 'seller-1',
    author_role: 'seller' as const,
    body: 'Tracking soon.',
    attachments: [],
    created_at: '2026-05-06T10:05:00Z',
    deleted_at: null,
    deleted_by_user_id: null,
    deleted_by_admin: false,
  },
];

describe('MessageThread', () => {
  beforeEach(() => {
    useAuthStore.getState().setAuth({ id: 'buyer-1' } as AuthUser, 'token');
    listMock.mockReset();
    postMock.mockReset();
    deleteMock.mockReset();
    uploadMock.mockReset();
  });

  it('renders both messages from the buyer perspective', async () => {
    listMock.mockResolvedValue({ data: messages, meta: { total: 2, has_more: false } });
    render(wrap(<MessageThread {...buyerView} />));

    await waitFor(() => expect(screen.getByText('Hi.')).toBeInTheDocument());
    expect(screen.getByText('Tracking soon.')).toBeInTheDocument();
    expect(screen.getByText('Revive Boutique')).toBeInTheDocument();
  });

  it('optimistically appends the message and rolls back on failure', async () => {
    listMock.mockResolvedValue({ data: messages, meta: { total: 2, has_more: false } });
    let rejectPost: (e: Error) => void = () => {};
    postMock.mockImplementation(
      () =>
        new Promise((_resolve, reject) => {
          rejectPost = reject;
        }),
    );

    render(wrap(<MessageThread {...buyerView} />));
    await waitFor(() => expect(screen.getByText('Hi.')).toBeInTheDocument());

    fireEvent.change(screen.getByLabelText('Message'), {
      target: { value: 'Optimistic test' },
    });
    fireEvent.click(screen.getByRole('button', { name: /Send/ }));

    await waitFor(() =>
      expect(screen.getByText('Optimistic test')).toBeInTheDocument(),
    );

    rejectPost(new Error('boom'));

    await waitFor(() =>
      expect(screen.queryByText('Optimistic test')).not.toBeInTheDocument(),
    );
  });

  it('shows error banner if list fails', async () => {
    listMock.mockRejectedValue(new Error('forbidden'));
    render(wrap(<MessageThread {...buyerView} />));

    await waitFor(() =>
      expect(screen.getByText(/Couldn't load/i)).toBeInTheDocument(),
    );
  });

  it('posts the message with attachment_ids when uploader staged a file', async () => {
    listMock.mockResolvedValue({ data: messages, meta: { total: 2, has_more: false } });
    uploadMock.mockResolvedValue({
      data: { id: 'att-1', url: '/u1.jpg', content_type: 'image/jpeg', size_bytes: 100 },
    });
    postMock.mockResolvedValue({ data: messages[0] });

    render(wrap(<MessageThread {...buyerView} />));
    await waitFor(() => expect(screen.getByText('Hi.')).toBeInTheDocument());

    const file = new File(['x'], 'damage.jpg', { type: 'image/jpeg' });
    fireEvent.change(screen.getByLabelText(/Attach images/i), {
      target: { files: [file] },
    });
    await waitFor(() => expect(uploadMock).toHaveBeenCalledTimes(1));

    fireEvent.change(screen.getByLabelText('Message'), {
      target: { value: 'with attachment' },
    });
    fireEvent.click(screen.getByRole('button', { name: /^Send$/ }));

    await waitFor(() => expect(postMock).toHaveBeenCalled());
    const [, body] = postMock.mock.calls[0];
    expect(body).toEqual({ body: 'with attachment', attachment_ids: ['att-1'] });
  });
});
