import { describe, it, expect, beforeEach, vi } from 'vitest';
import { screen, fireEvent, waitFor } from '@testing-library/react';
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 { Routes, Route } from 'react-router-dom';
import { api } from '../lib/api';
import BranchWorkingHoursPage from './BranchWorkingHoursPage';

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

const branch = {
  id: '1', uuid: 'b1', type: 'clinic', clinic_id: 3, clinic_name: 'کلینیک ما',
  name: 'شعبهٔ مرکزی', map: { latitude: null, longitude: null },
  address: 'خیابان اول', telephone: '03511111111', active: true,
  timezone: 'Asia/Tehran', city: null, province: null,
  working_hours_defined: true, rooms_count: 0,
};

function emptyDays(): Record<string, unknown[]> {
  return Object.fromEntries(Array.from({ length: 7 }, (_, d) => [String(d), []]));
}

function mockApi(days: Record<string, unknown[]>) {
  get.mockImplementation((path: string) => {
    if (path === '/api/v1/branches') return Promise.resolve({ success: true, data: [branch] });
    if (path.endsWith('/working-hours')) {
      return Promise.resolve({
        success: true,
        data: { branch_uuid: 'b1', timezone: 'Asia/Tehran', defined: true, days },
      });
    }
    return Promise.resolve({ success: true, data: null });
  });
  put.mockResolvedValue({ success: true, data: { branch_uuid: 'b1', timezone: 'Asia/Tehran', defined: true, days } });
}

function renderPage() {
  return renderWithProviders(
    <Routes>
      <Route path="/admin/branches/:branchUuid/working-hours" element={<BranchWorkingHoursPage />} />
    </Routes>,
    { route: '/admin/branches/b1/working-hours' },
  );
}

describe('BranchWorkingHoursPage', () => {
  beforeEach(() => {
    vi.clearAllMocks();
  });

  it('renders all seven days and marks the empty ones closed', async () => {
    mockApi(emptyDays());
    renderPage();

    await waitFor(() => expect(screen.getByText('شنبه')).toBeInTheDocument());
    expect(screen.getByText('جمعه')).toBeInTheDocument();
    expect(screen.getAllByText('بسته')).toHaveLength(7);
  });

  it('shows stored ranges as times, converting minutes from midnight', async () => {
    const days = emptyDays();
    days['0'] = [{ sequence: 0, start_minute: 540, end_minute: 780, start_time: '09:00', end_time: '13:00', active: true }];
    mockApi(days);
    renderPage();

    await waitFor(() => expect(screen.getByDisplayValue('09:00')).toBeInTheDocument());
    expect(screen.getByDisplayValue('13:00')).toBeInTheDocument();
  });

  /**
   * `<input type="time">` سقفش ۲۳:۵۹ است، پس ۱۴۴۰ با پرچم «تا پایان روز» نمایش داده
   * می‌شود و همان ۱۴۴۰ برمی‌گردد — وگرنه اولین ذخیره بازهٔ شبانه‌روزی را خراب می‌کرد.
   */
  it('keeps an all-day range at 1440 through a round trip', async () => {
    const days = emptyDays();
    days['3'] = [{ sequence: 0, start_minute: 0, end_minute: 1440, start_time: '00:00', end_time: '24:00', active: true }];
    mockApi(days);
    renderPage();

    await waitFor(() => expect(screen.getByText('۲۴:۰۰')).toBeInTheDocument());
    expect((screen.getByLabelText('تا پایان روز') as HTMLInputElement).checked).toBe(true);

    fireEvent.click(screen.getByText('ذخیرهٔ هفته'));

    await waitFor(() => expect(put).toHaveBeenCalled());
    expect(put.mock.calls[0][1].days['3']).toEqual([{ start_minute: 0, end_minute: 1440 }]);
  });

  it('turns a normal range into an all-day one when the flag is checked', async () => {
    const days = emptyDays();
    days['6'] = [{ sequence: 0, start_minute: 540, end_minute: 660, start_time: '09:00', end_time: '11:00', active: true }];
    mockApi(days);
    renderPage();

    await waitFor(() => expect(screen.getByDisplayValue('11:00')).toBeInTheDocument());
    fireEvent.click(screen.getByLabelText('تا پایان روز'));
    fireEvent.click(screen.getByText('ذخیرهٔ هفته'));

    await waitFor(() => expect(put).toHaveBeenCalled());
    expect(put.mock.calls[0][1].days['6']).toEqual([{ start_minute: 540, end_minute: 1440 }]);
  });

  it('sends minutes, not time strings, on save', async () => {
    const days = emptyDays();
    days['1'] = [{ sequence: 0, start_minute: 600, end_minute: 720, start_time: '10:00', end_time: '12:00', active: true }];
    mockApi(days);
    renderPage();

    await waitFor(() => expect(screen.getByDisplayValue('10:00')).toBeInTheDocument());
    fireEvent.click(screen.getByText('ذخیرهٔ هفته'));

    await waitFor(() => expect(put).toHaveBeenCalled());
    const [path, body] = put.mock.calls[0];
    expect(path).toBe('/api/v1/branch/b1/working-hours');
    expect(body.days['1']).toEqual([{ start_minute: 600, end_minute: 720 }]);
    // هر هفت روز فرستاده می‌شود، چون PUT جایگزینی کامل است نه merge تفاضلی.
    expect(Object.keys(body.days)).toHaveLength(7);
  });

  it('blocks a save whose end is not after its start, without calling the API', async () => {
    const days = emptyDays();
    days['2'] = [{ sequence: 0, start_minute: 600, end_minute: 720, start_time: '10:00', end_time: '12:00', active: true }];
    mockApi(days);
    renderPage();

    await waitFor(() => expect(screen.getByDisplayValue('12:00')).toBeInTheDocument());
    fireEvent.change(screen.getByDisplayValue('12:00'), { target: { value: '09:00' } });
    fireEvent.click(screen.getByText('ذخیرهٔ هفته'));

    await waitFor(() => expect(screen.getByText(/پایان بازه باید بعد از شروع/)).toBeInTheDocument());
    expect(put).not.toHaveBeenCalled();
  });

  it('copies one day onto the whole week', async () => {
    const days = emptyDays();
    days['0'] = [{ sequence: 0, start_minute: 480, end_minute: 600, start_time: '08:00', end_time: '10:00', active: true }];
    mockApi(days);
    renderPage();

    await waitFor(() => expect(screen.getByDisplayValue('08:00')).toBeInTheDocument());
    fireEvent.click(screen.getByText('اعمال روی همهٔ روزها'));

    expect(screen.getAllByDisplayValue('08:00')).toHaveLength(7);
    expect(screen.queryByText('بسته')).not.toBeInTheDocument();
  });

  it('removes a range so the day becomes closed', async () => {
    const days = emptyDays();
    days['4'] = [{ sequence: 0, start_minute: 540, end_minute: 660, start_time: '09:00', end_time: '11:00', active: true }];
    mockApi(days);
    renderPage();

    await waitFor(() => expect(screen.getByDisplayValue('11:00')).toBeInTheDocument());
    fireEvent.click(screen.getByLabelText('حذف بازه'));

    expect(screen.getAllByText('بسته')).toHaveLength(7);
  });
});
