import { describe, it, expect, vi, beforeAll, beforeEach, afterEach } from 'vitest';
import { waitFor } from '@testing-library/react';
import { fetchClient } from '@ablogcms/fetch-client';
import { registerAcmsElements } from './register';
import { upgradeLegacyBlockEditors } from './upgrade-legacy-marks';

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

vi.mock('@ablogcms/block-editor/block-editor', () => ({
  default: ({ defaultValue }: { defaultValue?: string }) => (
    <div data-testid="block-editor-stub">
      <span data-testid="default-value">{defaultValue}</span>
    </div>
  ),
}));

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

describe('upgradeLegacyBlockEditors', () => {
  beforeAll(() => {
    registerAcmsElements();
  });

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

  it('マークが存在しない場合は何もしない', () => {
    setupAcmsConfig();
    document.body.innerHTML = '<div class="js-other"></div>';

    upgradeLegacyBlockEditors(document);

    expect(document.querySelector('acms-block-editor')).toBeNull();
  });

  it('.js-block-editor マークを <acms-block-editor> に置換し、旧構造（data-target 先のマウント用コンテナ）を除去して新構造（hidden input のみ）へ変換する', async () => {
    setupAcmsConfig();
    document.body.innerHTML = `
      <div
        id="my-field"
        class="js-block-editor acms-admin-form-box"
        style="color: red;"
        data-target=".js-target"
        data-html=".js-html"
      >
        <div class="js-target"></div>
        <input type="hidden" class="js-html" value="<p>legacy</p>" />
        <input type="hidden" name="field[]" value="body" />
      </div>
    `;

    upgradeLegacyBlockEditors(document);

    const upgraded = document.querySelector('acms-block-editor');
    expect(upgraded).not.toBeNull();
    expect(document.querySelector('.js-block-editor')).toBeNull();
    // class は legacy マーク（js-block-editor）除去後、残った acms-admin-form-box が
    // root-class（<acms-block-editor> 自身の独自属性）として引き継がれる。標準の class は
    // 一切設定しない（host は display: contents でボックスを持たないため）。
    expect(upgraded?.hasAttribute('class')).toBe(false);
    expect(upgraded?.getAttribute('root-class')).toBe('acms-admin-form-box');
    expect(upgraded?.querySelector('[data-block-editor-mount]')?.className).toBe('acms-admin-form-box');
    expect(upgraded?.getAttribute('id')).toBe('my-field');
    expect(upgraded?.getAttribute('style')).toBe('color: red;');
    // 新構造では target/html というマウント先セレクタ属性を持たないため除去する
    expect(upgraded?.hasAttribute('data-target')).toBe(false);
    expect(upgraded?.hasAttribute('data-html')).toBe(false);
    // マウント先だった空のコンテナ（.js-target）は除去する
    expect(upgraded?.querySelector('.js-target')).toBeNull();
    // フォーム送信用の hidden input（.js-html 以外も含む）は移送されたまま残す
    expect(upgraded?.querySelector('input[name="field[]"]')).not.toBeNull();

    await waitFor(() => {
      expect(getStub()).not.toBeNull();
    });
    // data-html が指していた input（.js-html）の値が defaultValue として使われる
    expect(document.querySelector('[data-testid="default-value"]')?.textContent).toBe('<p>legacy</p>');
  });

  it('二度呼んでも重複しない（冪等）', async () => {
    setupAcmsConfig();
    document.body.innerHTML = `
      <div class="js-block-editor" data-target=".js-target" data-html=".js-html">
        <div class="js-target"></div>
        <input type="hidden" class="js-html" value="" />
      </div>
    `;

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

    upgradeLegacyBlockEditors(document);

    expect(document.querySelectorAll('acms-block-editor')).toHaveLength(1);
  });

  it('data-target 先のコンテナに既にコンテンツがある場合は置換しない', () => {
    setupAcmsConfig();
    document.body.innerHTML = `
      <div class="js-block-editor" data-target=".js-target" data-html=".js-html">
        <div class="js-target"><span>already rendered</span></div>
        <input type="hidden" class="js-html" value="" />
      </div>
    `;

    upgradeLegacyBlockEditors(document);

    expect(document.querySelector('acms-block-editor')).toBeNull();
    expect(document.querySelector('.js-block-editor')).not.toBeNull();
  });

  it('ACMS.Config.blockEditorMark をカスタム値にしても動く', () => {
    setupAcmsConfig('.my-block-editor');
    document.body.innerHTML = `
      <div class="my-block-editor" data-target=".js-target" data-html=".js-html">
        <div class="js-target"></div>
        <input type="hidden" class="js-html" value="" />
      </div>
    `;

    upgradeLegacyBlockEditors(document);

    const upgraded = document.querySelector('acms-block-editor');
    expect(upgraded).not.toBeNull();
    expect(upgraded?.tagName.toLowerCase()).toBe('acms-block-editor');

    // カスタムマークの場合、除去対象はデフォルトの js-block-editor クラスではないため
    // クラス自体は残り得るが、置換済み要素はタグ名で判定して弾くため二重処理にはならない
    upgradeLegacyBlockEditors(document);
    expect(document.querySelectorAll('acms-block-editor')).toHaveLength(1);
  });

  it('blockEditorMark をクラス名にすると host の root-class がマウントコンテナの class として転送されるが、転送先（子孫）は同じセレクタに一致し得るため再処理しない', () => {
    setupAcmsConfig('.my-block-editor');
    document.body.innerHTML = `
      <div class="my-block-editor" data-target=".js-target" data-html=".js-html">
        <div class="js-target"></div>
        <input type="hidden" class="js-html" value="" />
      </div>
    `;

    upgradeLegacyBlockEditors(document);

    const upgraded = document.querySelector('acms-block-editor');
    // host（acms-block-editor 自身）は標準の class を一切持たない。旧 class は root-class
    // という独自属性として引き継がれる。
    expect(upgraded?.hasAttribute('class')).toBe(false);
    expect(upgraded?.getAttribute('root-class')).toBe('my-block-editor');
    // 転送先（マウントコンテナ）が blockEditorMark と同じセレクタに一致する状態を作った上で、
    // 再走査してもその子孫が「未変換の legacy 要素」として誤って acms-block-editor 化されないことを確認する。
    expect(document.querySelectorAll('.my-block-editor')).toHaveLength(1);
    expect(upgraded?.querySelector('.my-block-editor')).not.toBeNull();

    upgradeLegacyBlockEditors(document);
    expect(document.querySelectorAll('acms-block-editor')).toHaveLength(1);
  });

  it('サブツリーを指定すると範囲外の要素には影響しない', () => {
    setupAcmsConfig();
    document.body.innerHTML = `
      <div id="outside" class="js-block-editor" data-target=".js-target" data-html=".js-html">
        <div class="js-target"></div>
        <input type="hidden" class="js-html" value="" />
      </div>
      <div id="scope">
        <div id="inside" class="js-block-editor" data-target=".js-target" data-html=".js-html">
          <div class="js-target"></div>
          <input type="hidden" class="js-html" value="" />
        </div>
      </div>
    `;
    const scope = document.getElementById('scope')!;

    upgradeLegacyBlockEditors(scope);

    expect(document.getElementById('outside')).not.toBeNull();
    expect(document.getElementById('outside')?.tagName.toLowerCase()).not.toBe('acms-block-editor');
    expect(scope.querySelector('acms-block-editor')).not.toBeNull();
  });

  it('data-settings は data- を外した settings 属性（<acms-block-editor> 自身の公開 API）として転送される', () => {
    setupAcmsConfig();
    document.body.innerHTML = `
      <div class="js-block-editor" data-target=".js-target" data-html=".js-html" data-settings='{"features":{"textItalic":false}}'>
        <div class="js-target"></div>
        <input type="hidden" class="js-html" value="" />
      </div>
    `;

    upgradeLegacyBlockEditors(document);

    const upgraded = document.querySelector('acms-block-editor');
    expect(upgraded?.hasAttribute('data-settings')).toBe(false);
    expect(upgraded?.getAttribute('settings')).toBe('{"features":{"textItalic":false}}');
  });

  it('data-settings 以外の data-* 属性も同様に data- を外して転送する（すべて BlockEditor の props にそのまま渡る）', () => {
    setupAcmsConfig();
    document.body.innerHTML = `
      <div class="js-block-editor" data-target=".js-target" data-html=".js-html" data-content="tooltip text">
        <div class="js-target"></div>
        <input type="hidden" class="js-html" value="" />
      </div>
    `;

    upgradeLegacyBlockEditors(document);

    const upgraded = document.querySelector('acms-block-editor');
    expect(upgraded?.hasAttribute('data-content')).toBe(false);
    expect(upgraded?.getAttribute('content')).toBe('tooltip text');
  });

  it('data-target 属性が無い（既に新形式で書かれた）マーク要素はコンテナ除去をスキップしてそのまま移送する', async () => {
    setupAcmsConfig();
    document.body.innerHTML = `
      <div class="js-block-editor">
        <input type="hidden" value="<p>already new format</p>" />
      </div>
    `;

    upgradeLegacyBlockEditors(document);

    const upgraded = document.querySelector('acms-block-editor');
    expect(upgraded).not.toBeNull();

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