import { describe, it, expect, vi, beforeEach } from 'vitest';
import { screen, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { renderWithProviders } from '../test/utils';

vi.mock('../lib/api', () => ({
  api: { get: vi.fn(), post: vi.fn(), patch: vi.fn(), put: vi.fn(), delete: vi.fn() },
  ApiError: class extends Error {},
}));

vi.mock('sonner', () => ({ toast: { success: vi.fn(), error: vi.fn() } }));

import { api } from '../lib/api';
import CancelAppointmentDialog from './CancelAppointmentDialog';

const get = api.get as ReturnType<typeof vi.fn>;
const post = api.post as ReturnType<typeof vi.fn>;

const preview = (over: Record<string, unknown> = {}) => ({
  data: {
    penalty_rials: 250_000,
    deposit_refundable: true,
    credit_refundable: true,
    within_free_window: false,
    notes: [],
    paid_rials: 1_000_000,
    ...over,
  },
});

describe('CancelAppointmentDialog', () => {
  beforeEach(() => {
    vi.clearAllMocks();
    post.mockResolvedValue({ data: { waitlist_notified: 0 } });
  });

  /** ⭐ عددی که اپراتور می‌بیند باید همان باشد که کسر می‌شود. */
  it('shows the penalty from the server before anything is cancelled', async () => {
    get.mockResolvedValue(preview());

    renderWithProviders(
      <CancelAppointmentDialog open appointmentUuid="a-1" onClose={() => {}} />,
    );

    // مبلغ‌ها به تومان نمایش داده می‌شوند: ۲۵۰٬۰۰۰ ریال ⇒ ۲۵٬۰۰۰ تومان.
    await waitFor(() => expect(screen.getByText(/۲۵٬۰۰۰ تومان/)).toBeInTheDocument());
    expect(post).not.toHaveBeenCalled();
  });

  it('says "بدون جریمه" inside the free window instead of showing a zero', async () => {
    get.mockResolvedValue(preview({ penalty_rials: 0, within_free_window: true }));

    renderWithProviders(
      <CancelAppointmentDialog open appointmentUuid="a-1" onClose={() => {}} />,
    );

    await waitFor(() => expect(screen.getByText('بدون جریمه')).toBeInTheDocument());
    expect(screen.getByText('در بازهٔ لغو رایگان است.')).toBeInTheDocument();
  });

  it('sends the typed reason along with the cancellation', async () => {
    get.mockResolvedValue(preview());
    const user = userEvent.setup();

    renderWithProviders(
      <CancelAppointmentDialog open appointmentUuid="a-1" onClose={() => {}} />,
    );

    await waitFor(() => expect(screen.getByText(/مبلغ پرداخت‌شده/)).toBeInTheDocument());

    await user.type(screen.getByLabelText(/دلیل لغو/), 'بیمار تماس گرفت');
    await user.click(screen.getByRole('button', { name: 'لغو نوبت' }));

    await waitFor(() =>
      expect(post).toHaveBeenCalledWith('/api/v1/appointment/a-1/cancel', {
        by: 'doctor',
        reason: 'بیمار تماس گرفت',
      }),
    );
  });

  /** پیش‌نمایشی که نیامده نباید لغو را قفل کند — ولی باید صریح بگوید که نیامده. */
  it('still allows cancelling when the preview fails, and says so', async () => {
    get.mockRejectedValue(new Error('down'));

    renderWithProviders(
      <CancelAppointmentDialog open appointmentUuid="a-1" onClose={() => {}} />,
    );

    await waitFor(() =>
      expect(screen.getByText(/پیامد مالی لغو در دسترس نیست/)).toBeInTheDocument(),
    );

    expect(screen.getByRole('button', { name: 'لغو نوبت' })).not.toBeDisabled();
  });
});
