// @vitest-environment jsdom
/**
 * Component tests for PackageRequestApproval — the inline Approve/Deny card
 * shown in chat when the agent paused on a RequestPackage. Network
 * (fetchPackageRequests / decidePackageRequest) is fully mocked; i18n uses the
 * real instance so labels resolve.
 */
import '../../test/dom-setup';
import '../../i18n';
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 type { PackageRequest } from '../../api';
import * as api from '../../api';
import { PackageRequestApproval } from './PackageRequestApproval';

vi.mock('../../api', async (importOriginal) => {
  const actual = await importOriginal<typeof import('../../api')>();
  return {
    ...actual,
    fetchPackageRequests: vi.fn(),
    decidePackageRequest: vi.fn(),
  };
});

const mockedFetch = vi.mocked(api.fetchPackageRequests);
const mockedDecide = vi.mocked(api.decidePackageRequest);

function req(overrides: Partial<PackageRequest> = {}): PackageRequest {
  return {
    id: 'req-1',
    taskId: '7',
    jobId: 'job-1',
    spaceId: 'space-1',
    pieceName: 'chat',
    movementName: 'execute',
    spec: 'requests==2.32.3',
    normalizedName: 'requests',
    reason: 'need http',
    status: 'pending',
    decidedBy: null,
    createdAt: '2026-07-06T00:00:00Z',
    decidedAt: null,
    ...overrides,
  };
}

describe('PackageRequestApproval', () => {
  beforeEach(() => {
    mockedFetch.mockReset();
    mockedDecide.mockReset();
  });

  it('renders nothing when there are no pending requests', async () => {
    mockedFetch.mockResolvedValue([req({ status: 'approved' })]);
    const { container } = renderWithProviders(<PackageRequestApproval taskId={7} poll={false} />);
    await waitFor(() => expect(mockedFetch).toHaveBeenCalledWith(7));
    expect(container.querySelector('[data-testid="package-request-approval"]')).toBeNull();
  });

  it('renders a card per pending request with spec + reason', async () => {
    mockedFetch.mockResolvedValue([
      req({ id: 'r1', spec: 'requests==2.32.3', normalizedName: 'requests', reason: 'http calls' }),
      req({ id: 'r2', spec: 'pandas', normalizedName: 'pandas', reason: null }),
    ]);
    renderWithProviders(<PackageRequestApproval taskId={7} poll={false} />);
    expect(await screen.findByTestId('package-request-requests')).toBeInTheDocument();
    expect(screen.getByTestId('package-request-pandas')).toBeInTheDocument();
    expect(screen.getByText(/requests==2.32.3/)).toBeInTheDocument();
    expect(screen.getByText(/http calls/)).toBeInTheDocument();
  });

  it('Approve calls decidePackageRequest with approve + id', async () => {
    mockedFetch.mockResolvedValue([req({ id: 'r1', normalizedName: 'requests' })]);
    mockedDecide.mockResolvedValue(undefined);
    const user = userEvent.setup();
    renderWithProviders(<PackageRequestApproval taskId={7} poll={false} />);
    await user.click(await screen.findByTestId('package-request-approve-requests'));
    await waitFor(() => expect(mockedDecide).toHaveBeenCalledWith(7, 'r1', 'approve'));
  });

  it('shows an error row when the decide mutation rejects', async () => {
    mockedFetch.mockResolvedValue([req({ id: 'r1', normalizedName: 'requests' })]);
    mockedDecide.mockRejectedValue(new Error('install failed'));
    const user = userEvent.setup();
    renderWithProviders(<PackageRequestApproval taskId={7} poll={false} />);
    await user.click(await screen.findByTestId('package-request-approve-requests'));
    expect(await screen.findByTestId('package-request-error')).toHaveTextContent('install failed');
  });
});
