// @vitest-environment jsdom
/**
 * Component tests for PythonPackagesPanel (per-space Python package UI).
 *
 * api + App.useAuthState are mocked so no real network and canManage=true.
 */
import '../../test/dom-setup';
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/render-helpers';
import i18n from '../../i18n';

const { fetchMock, addMock, removeMock, fetchMembersMock } = vi.hoisted(() => ({
  fetchMock: vi.fn(),
  addMock: vi.fn(),
  removeMock: vi.fn(),
  fetchMembersMock: vi.fn(),
}));

vi.mock('../../api', () => ({
  fetchSpacePythonPackages: fetchMock,
  addSpacePythonPackage: addMock,
  removeSpacePythonPackage: removeMock,
  fetchSpaceMembers: fetchMembersMock,
}));

vi.mock('../../App', () => ({
  useAuthState: () => ({ mode: 'disabled' as const }),
}));

import { PythonPackagesPanel } from './PythonPackagesPanel';

const ENABLED = {
  enabled: true,
  indexUrl: 'https://pypi.org/simple',
  maxPackagesPerSpace: 30,
  preflight: { ok: true },
  packages: [{ name: 'requests', spec: 'requests==2.32.3', addedAt: '2026-07-02T00:00:00Z' }],
};

beforeEach(() => {
  vi.clearAllMocks();
  void i18n.changeLanguage('ja');
  fetchMembersMock.mockResolvedValue([]);
  fetchMock.mockResolvedValue(structuredClone(ENABLED));
  addMock.mockResolvedValue({ packages: [] });
  removeMock.mockResolvedValue({ packages: [] });
});

describe('PythonPackagesPanel', () => {
  it('lists installed packages by their spec', async () => {
    renderWithProviders(<PythonPackagesPanel spaceId="s1" />);
    await waitFor(() => expect(screen.getByText('requests==2.32.3')).toBeInTheDocument());
  });

  it('submits a typed spec via addSpacePythonPackage', async () => {
    renderWithProviders(<PythonPackagesPanel spaceId="s1" />);
    await waitFor(() => expect(screen.getByTestId('python-add-input')).toBeInTheDocument());
    await userEvent.type(screen.getByTestId('python-add-input'), 'httpx==0.27.0');
    await userEvent.keyboard('{Enter}');
    await waitFor(() => expect(addMock).toHaveBeenCalledWith('s1', 'httpx==0.27.0'));
  });

  it('removes a package via removeSpacePythonPackage', async () => {
    renderWithProviders(<PythonPackagesPanel spaceId="s1" />);
    await waitFor(() => expect(screen.getByText('requests==2.32.3')).toBeInTheDocument());
    await userEvent.click(screen.getByText('削除'));
    await waitFor(() => expect(removeMock).toHaveBeenCalledWith('s1', 'requests'));
  });

  it('shows a disabled notice and blocks the input when the feature is off', async () => {
    fetchMock.mockResolvedValue({ ...structuredClone(ENABLED), enabled: false, preflight: { ok: false, reason: 'feature disabled' } });
    renderWithProviders(<PythonPackagesPanel spaceId="s1" />);
    await waitFor(() => expect(screen.getByTestId('python-add-input')).toBeDisabled());
  });
});
