/* @vitest-environment jsdom */

import { fireEvent, render, screen } from '@testing-library/react';
import { afterEach, beforeEach, expect, test, vi } from 'vitest';

import { BabyLoveDrawingRuntimeShell } from './BabyLoveDrawingRuntimeShell';

const saveBabyLoveDrawingMock = vi.fn();
const createBabyLoveDrawingMagicImageMock = vi.fn();

const mocapMock = vi.hoisted(() => ({
  command: null as null | {
    actions: string[];
    leftHand?: {
      x: number;
      y: number;
      state: 'open_palm' | 'grab' | 'unknown';
      side: 'left';
    } | null;
    rightHand?: {
      x: number;
      y: number;
      state: 'open_palm' | 'grab' | 'unknown';
      side: 'right';
    } | null;
  },
}));

vi.mock('../../services/useMocapInput', () => ({
  useMocapInput: () => ({
    status: 'idle',
    latestCommand: mocapMock.command,
    rawPacketPreview: null,
    error: null,
  }),
}));

vi.mock('../../services/edutainment-baby-drawing', () => ({
  createBabyLoveDrawingMagicImage: (...args: unknown[]) =>
    createBabyLoveDrawingMagicImageMock(...args),
  saveBabyLoveDrawing: (...args: unknown[]) => saveBabyLoveDrawingMock(...args),
}));

function installCanvasMock() {
  const context = {
    save: vi.fn(),
    restore: vi.fn(),
    beginPath: vi.fn(),
    moveTo: vi.fn(),
    lineTo: vi.fn(),
    stroke: vi.fn(),
    fillRect: vi.fn(),
    drawImage: vi.fn(),
    set fillStyle(_value: string) {},
    set strokeStyle(_value: string) {},
    set lineWidth(_value: number) {},
    set lineCap(_value: CanvasLineCap) {},
    set lineJoin(_value: CanvasLineJoin) {},
    set globalCompositeOperation(_value: GlobalCompositeOperation) {},
  } as unknown as CanvasRenderingContext2D;

  vi.spyOn(HTMLCanvasElement.prototype, 'getContext').mockReturnValue(context);
  vi.spyOn(HTMLCanvasElement.prototype, 'toDataURL').mockReturnValue(
    'data:image/png;base64,original',
  );
}

beforeEach(() => {
  installCanvasMock();
  mocapMock.command = null;
  saveBabyLoveDrawingMock.mockReturnValue({
    record: {
      drawingId: 'baby-love-drawing-local-1',
      templateId: 'baby-love-drawing',
      templateName: '宝贝爱画',
      originalImageSrc: 'data:image/png;base64,original',
      magicImageSrc: null,
      strokeTrace: [],
      saveMode: 'original-only',
      themeTags: ['寓教于乐', '宝贝爱画'],
      createdAt: '2026-05-13T08:00:00.000Z',
      updatedAt: '2026-05-13T08:00:00.000Z',
    },
  });
  createBabyLoveDrawingMagicImageMock.mockResolvedValue({
    magicImageSrc: 'data:image/png;base64,magic',
    generationProvider: 'vector-engine-gpt-image-2',
    prompt: '绘本风格',
  });
});

afterEach(() => {
  vi.restoreAllMocks();
});

test('renders drawing board, seven colors and tool buttons', () => {
  render(<BabyLoveDrawingRuntimeShell />);

  expect(screen.getByTestId('baby-love-drawing-runtime')).toBeTruthy();
  expect(screen.getByLabelText('画板')).toBeTruthy();
  expect(screen.getByLabelText('红')).toBeTruthy();
  expect(screen.getByLabelText('紫')).toBeTruthy();
  expect(screen.getAllByRole('button')).toHaveLength(11);
  expect(screen.getByLabelText('画笔')).toBeTruthy();
  expect(screen.getByLabelText('橡皮')).toBeTruthy();
});

test('finish then save stores original drawing in local demo service', () => {
  render(<BabyLoveDrawingRuntimeShell />);

  fireEvent.click(screen.getByRole('button', { name: '完成' }));
  fireEvent.click(screen.getByRole('button', { name: '保存' }));

  expect(saveBabyLoveDrawingMock).toHaveBeenCalledWith(
    expect.objectContaining({
      originalImageSrc: 'data:image/png;base64,original',
      magicImageSrc: null,
    }),
  );
  expect(screen.getByText('已保存')).toBeTruthy();
});

test('back button calls onBack callback', () => {
  const onBack = vi.fn();
  render(<BabyLoveDrawingRuntimeShell onBack={onBack} />);

  fireEvent.click(screen.getByLabelText('返回'));

  expect(onBack).toHaveBeenCalledTimes(1);
});

test('mocap camera-left hand drives the player right hand brush cursor', () => {
  mocapMock.command = {
    actions: [],
    leftHand: { x: 0.72, y: 0.34, state: 'open_palm', side: 'left' },
    rightHand: null,
  };
  const { container, rerender } = render(<BabyLoveDrawingRuntimeShell />);
  const cursor = container.querySelector(
    '.baby-love-drawing-runtime__cursor',
  ) as HTMLElement;

  expect(cursor.style.left).toBe('72%');
  expect(cursor.style.top).toBe('34%');

  mocapMock.command = {
    actions: [],
    leftHand: null,
    rightHand: { x: 0.18, y: 0.82, state: 'grab', side: 'right' },
  };
  rerender(<BabyLoveDrawingRuntimeShell />);

  expect(cursor.style.left).toBe('72%');
  expect(cursor.style.top).toBe('34%');
});

test('mocap camera-right hand renders the player left hand color indicator', () => {
  mocapMock.command = {
    actions: [],
    leftHand: null,
    rightHand: { x: 0.16, y: 0.42, state: 'open_palm', side: 'right' },
  };
  const { container } = render(<BabyLoveDrawingRuntimeShell />);
  const indicator = container.querySelector(
    '.baby-love-drawing-runtime__left-hand-indicator',
  ) as HTMLElement;

  expect(indicator).toBeTruthy();
  expect(indicator.style.left).toBe('16%');
  expect(indicator.style.top).toBe('42%');
});

test('left hand indicator stays visible through brief mocap hand loss', () => {
  vi.useFakeTimers();
  mocapMock.command = {
    actions: [],
    leftHand: null,
    rightHand: { x: 0.16, y: 0.42, state: 'open_palm', side: 'right' },
  };
  const { container, rerender } = render(<BabyLoveDrawingRuntimeShell />);

  vi.advanceTimersByTime(120);
  mocapMock.command = {
    actions: [],
    leftHand: null,
    rightHand: null,
  };
  rerender(<BabyLoveDrawingRuntimeShell />);

  const indicator = container.querySelector(
    '.baby-love-drawing-runtime__left-hand-indicator',
  ) as HTMLElement;
  expect(indicator).toBeTruthy();
  expect(indicator.style.left).toBe('16%');
  expect(indicator.style.top).toBe('42%');
});

test('player left hand never takes over the right hand brush cursor', () => {
  mocapMock.command = {
    actions: [],
    leftHand: { x: 0.68, y: 0.32, state: 'open_palm', side: 'left' },
    rightHand: { x: 0.18, y: 0.78, state: 'open_palm', side: 'right' },
  };
  const { container, rerender } = render(<BabyLoveDrawingRuntimeShell />);
  const cursor = container.querySelector(
    '.baby-love-drawing-runtime__cursor',
  ) as HTMLElement;

  expect(cursor.style.left).toBe('68%');
  expect(cursor.style.top).toBe('32%');

  mocapMock.command = {
    actions: [],
    leftHand: null,
    rightHand: { x: 0.7, y: 0.3, state: 'grab', side: 'right' },
  };
  rerender(<BabyLoveDrawingRuntimeShell />);

  expect(cursor.style.left).toBe('68%');
  expect(cursor.style.top).toBe('32%');
});

test('large camera-left jump is rejected to prevent left hand stealing brush', () => {
  mocapMock.command = {
    actions: [],
    leftHand: { x: 0.72, y: 0.34, state: 'open_palm', side: 'left' },
    rightHand: null,
  };
  const { container, rerender } = render(<BabyLoveDrawingRuntimeShell />);
  const cursor = container.querySelector(
    '.baby-love-drawing-runtime__cursor',
  ) as HTMLElement;

  expect(cursor.style.left).toBe('72%');
  expect(cursor.style.top).toBe('34%');

  mocapMock.command = {
    actions: [],
    leftHand: { x: 0.16, y: 0.82, state: 'grab', side: 'left' },
    rightHand: null,
  };
  rerender(<BabyLoveDrawingRuntimeShell />);

  expect(cursor.style.left).toBe('72%');
  expect(cursor.style.top).toBe('34%');
});
