import { describe, it, expect, vi, afterEach, beforeEach } from 'vitest';
import { waitFor, fireEvent } from '@testing-library/react';
import { fetchClient } from '@ablogcms/fetch-client';
import type { EditorSettings } from '@ablogcms/block-editor/types';
import { mountBlockEditorField } from './mount-block-editor';

vi.mock('@ablogcms/fetch-client', () => ({
  fetchClient: { get: vi.fn() },
}));

vi.mock('@ablogcms/block-editor/block-editor', () => ({
  default: ({
    defaultValue,
    onUpdate,
    onTransaction,
    editorSettings,
  }: {
    defaultValue?: string;
    onUpdate: (value: string) => void;
    onTransaction?: (props: { editor: { id: string } }) => void;
    editorSettings?: EditorSettings;
  }) => (
    <div data-testid="block-editor-stub">
      <span data-testid="default-value">{defaultValue}</span>
      <span data-testid="color-palette">{JSON.stringify(editorSettings?.colorPalette)}</span>
      <button type="button" onClick={() => onUpdate('<p>updated</p>')}>
        update
      </button>
      <button type="button" onClick={() => onTransaction?.({ editor: { id: 'fake-editor' } })}>
        transaction
      </button>
    </div>
  ),
}));

const getStub = () => document.querySelector('[data-testid="block-editor-stub"]');

describe('mountBlockEditorField', () => {
  beforeEach(() => {
    vi.mocked(fetchClient.get).mockReset();
    vi.mocked(fetchClient.get).mockResolvedValue({
      data: {},
      status: 200,
      statusText: 'OK',
      headers: new Headers(),
    });
  });

  afterEach(() => {
    document.body.innerHTML = '';
    vi.clearAllMocks();
    delete (window as unknown as { ACMS?: unknown }).ACMS;
  });

  const setupAcmsConfig = () => {
    (window as unknown as { ACMS: { Config: Record<string, unknown>; Library: unknown } }).ACMS = {
      Config: {
        bid: 1,
        cid: 2,
        blockEditorMark: '.js-block-editor',
        blockEditorConfig: { editorProps: {} },
      },
      Library: {
        acmsLink: vi.fn(() => 'ajax/edit/block-editor/settings.json'),
      },
    };
  };

  it('子孫に hidden input が無い場合は createProps 由来のエラーを投げる', () => {
    setupAcmsConfig();
    const host = document.createElement('div');
    expect(() => mountBlockEditorField(host)).toThrow('Not found hidden input element!');
  });

  it('自身をマウント先にし、子孫の hidden input の現在値を defaultValue にする', async () => {
    setupAcmsConfig();
    const host = document.createElement('div');
    host.innerHTML = `<input type="hidden" name="body" value="<p>hello</p>">`;
    document.body.appendChild(host);

    mountBlockEditorField(host);

    await waitFor(() => {
      expect(getStub()).not.toBeNull();
    });
    expect(document.querySelector('[data-testid="default-value"]')?.textContent).toBe('<p>hello</p>');
  });

  it('hidden input 以外の子要素が既にある場合はマウントしない（二重マウント防止）', () => {
    setupAcmsConfig();
    const host = document.createElement('div');
    host.innerHTML = `<div>already rendered</div><input type="hidden" name="body" value="">`;
    document.body.appendChild(host);

    const controller = mountBlockEditorField(host);

    expect(controller).toBeNull();
    expect(getStub()).toBeNull();
  });

  it('入力更新で hidden input の値が同期され、onChange が呼ばれる', async () => {
    setupAcmsConfig();
    const host = document.createElement('div');
    host.innerHTML = `<input type="hidden" name="body" value="">`;
    document.body.appendChild(host);
    const input = host.querySelector<HTMLInputElement>('input')!;
    const onChange = vi.fn();

    mountBlockEditorField(host, { onChange });

    await waitFor(() => {
      expect(getStub()).not.toBeNull();
    });

    fireEvent.click(document.querySelector('button')!);

    expect(input.value).toBe('<p>updated</p>');
    expect(onChange).toHaveBeenCalledTimes(1);
  });

  it('transaction のたびに onEditorReady が editor インスタンスを伴って呼ばれる', async () => {
    setupAcmsConfig();
    const host = document.createElement('div');
    host.innerHTML = `<input type="hidden" name="body" value="">`;
    document.body.appendChild(host);
    const onEditorReady = vi.fn();

    mountBlockEditorField(host, { onEditorReady });

    await waitFor(() => {
      expect(getStub()).not.toBeNull();
    });

    const buttons = document.querySelectorAll('button');
    fireEvent.click(buttons[1]);

    expect(onEditorReady).toHaveBeenCalledWith({ id: 'fake-editor' });
  });

  it('unmount するとコンテナが空になる', async () => {
    setupAcmsConfig();
    const host = document.createElement('div');
    host.innerHTML = `<input type="hidden" name="body" value="">`;
    document.body.appendChild(host);

    const controller = mountBlockEditorField(host);

    await waitFor(() => {
      expect(getStub()).not.toBeNull();
    });

    controller?.unmount();

    expect(getStub()).toBeNull();
  });

  it('取得した設定 (EditorSettings) が editorSettings として BlockEditor に渡る', async () => {
    setupAcmsConfig();
    // SWR はモジュールグローバルなキャッシュを使うため、他テストと同じ bid/cid だと
    // 既に解決済みのキャッシュ ({}) がヒットしてしまう。bid/cid を変えてキャッシュキーを分離する。
    (window as unknown as { ACMS: { Config: Record<string, unknown> } }).ACMS.Config.bid = 99;
    (window as unknown as { ACMS: { Config: Record<string, unknown> } }).ACMS.Config.cid = 99;
    vi.mocked(fetchClient.get).mockResolvedValueOnce({
      data: { colorPalette: ['#ff0000'] },
      status: 200,
      statusText: 'OK',
      headers: new Headers(),
    });
    const host = document.createElement('div');
    host.innerHTML = `<input type="hidden" name="body" value="">`;
    document.body.appendChild(host);

    mountBlockEditorField(host);

    await waitFor(() => {
      expect(document.querySelector('[data-testid="color-palette"]')?.textContent).toBe('["#ff0000"]');
    });
    expect(ACMS.Library.acmsLink).toHaveBeenCalledWith(
      { tpl: 'ajax/edit/block-editor/settings.json', bid: 99, cid: 99 },
      false
    );
  });
});
