import { act, render, renderHook } from '@testing-library/react';
import { afterEach, describe, expect, it, vi } from 'vitest';
import { getDataviewBulkActionRegistry } from '@ablogcms/plugins/core';
import useEntryAdminBulkActions, { type GetBulkActionsContext } from './use-entry-admin-bulk-actions';
import type { EntrySort } from '../types';
import { bulkChangeStatus, bulkDuplicate, bulkTrash } from '../api';

vi.mock('../api', () => ({
  bulkChangeBlog: vi.fn(async () => undefined),
  bulkChangeCategory: vi.fn(async () => undefined),
  bulkChangeStatus: vi.fn(async () => undefined),
  bulkChangeUser: vi.fn(async () => undefined),
  bulkDuplicate: vi.fn(async () => undefined),
  bulkTrash: vi.fn(async () => undefined),
  changeCategorySort: vi.fn(async () => undefined),
  changeSort: vi.fn(async () => undefined),
  changeUserSort: vi.fn(async () => undefined),
  exportEntries: vi.fn(async () => undefined),
}));

// UserSelect/CategorySelect/BlogSelect は SWR 経由の非同期データ取得を伴うため、
// Button/Select への置き換え確認に無関係な fetch モックを増やさないよう簡易スタブに差し替える。
vi.mock('@features/user/components/user-select/user-select', () => ({
  default: () => <div data-testid="user-select" />,
}));
vi.mock('@features/category/components/category-select/category-select', () => ({
  default: () => <div data-testid="category-select" />,
}));
vi.mock('@features/blog/components/blog-select/blog-select', () => ({
  default: () => <div data-testid="blog-select" />,
}));

const createContext = (bulkActions: string[]): GetBulkActionsContext => ({
  invalidate: vi.fn(),
  sort: {
    enabled: true,
    type: 'entry',
    context: null,
    permissions: { entry: true, user: true, category: true, otherUser: true },
  } as EntrySort,
  bulkActions,
});

const setup = (bulkActions: string[]) => {
  const context = createContext(bulkActions);
  const { result } = renderHook(() => useEntryAdminBulkActions({ getBulkActions: (actions) => actions, context }));
  return result.current;
};

// button.tsx の unstyled + className 合成モデルに従い、acms-admin-btn-admin 系クラスが
// 置き換え前と同一のまま DOM に出ることを固定する特徴付けテスト。
describe('useEntryAdminBulkActions', () => {
  it('order アクションのボタンが acms-admin-btn-admin acms-admin-btn-admin-primary を保持する', () => {
    const actions = setup(['order']);
    const action = actions.find((a) => a.id === 'order')!;
    const { container } = render(<>{action.renderForm!([])}</>);
    const button = container.querySelector('button')!;
    // unstyled は :where(.acms-admin-btn-unstyled) で無効化されるベーススタイルの上乗せなので、
    // acms-admin-btn/-unstyled が同居しても見た目には影響しない（button.tsx の合成モデル通り）。
    expect(button).toHaveClass('acms-admin-btn-admin', 'acms-admin-btn-admin-primary');
  });

  it('status アクションが Select（acms-admin-form-select）とボタンを保持する', () => {
    const actions = setup(['status']);
    const action = actions.find((a) => a.id === 'status')!;
    const { container } = render(<>{action.renderForm!([])}</>);
    const select = container.querySelector('select')!;
    const button = container.querySelector('button')!;
    expect(select).toHaveClass('acms-admin-form-select');
    expect(button).toHaveClass('acms-admin-btn-admin', 'acms-admin-btn-admin-primary', 'acms-admin-margin-left-mini');
  });

  it('user アクションのボタンが acms-admin-margin-left-mini を含めて維持される', () => {
    const actions = setup(['user']);
    const action = actions.find((a) => a.id === 'user')!;
    const { container } = render(<>{action.renderForm!([])}</>);
    const button = container.querySelector('button')!;
    expect(button).toHaveClass('acms-admin-btn-admin', 'acms-admin-btn-admin-primary', 'acms-admin-margin-left-mini');
  });

  it('category アクションのボタンが acms-admin-btn-admin acms-admin-btn-admin-primary を保持する', () => {
    const actions = setup(['category']);
    const action = actions.find((a) => a.id === 'category')!;
    const { container } = render(<>{action.renderForm!([])}</>);
    const button = container.querySelector('button')!;
    expect(button).toHaveClass('acms-admin-btn-admin', 'acms-admin-btn-admin-primary');
  });

  it('blog アクションのボタンが acms-admin-margin-left-mini を含めて維持される', () => {
    const actions = setup(['blog']);
    const action = actions.find((a) => a.id === 'blog')!;
    const { container } = render(<>{action.renderForm!([])}</>);
    const button = container.querySelector('button')!;
    expect(button).toHaveClass('acms-admin-btn-admin', 'acms-admin-btn-admin-primary', 'acms-admin-margin-left-mini');
  });

  it('duplicate アクションのボタンが acms-admin-btn-admin acms-admin-btn-admin-primary を保持する', () => {
    const actions = setup(['duplicate']);
    const action = actions.find((a) => a.id === 'duplicate')!;
    const { container } = render(<>{action.renderForm!([])}</>);
    const button = container.querySelector('button')!;
    expect(button).toHaveClass('acms-admin-btn-admin', 'acms-admin-btn-admin-primary');
  });

  it('export アクションのボタンが acms-admin-btn-admin acms-admin-btn-admin-primary を保持する', () => {
    const actions = setup(['export']);
    const action = actions.find((a) => a.id === 'export')!;
    const { container } = render(<>{action.renderForm!([])}</>);
    const button = container.querySelector('button')!;
    expect(button).toHaveClass('acms-admin-btn-admin', 'acms-admin-btn-admin-primary');
  });

  it('trash アクションのボタンが acms-admin-btn-admin acms-admin-btn-admin-danger を保持し確認ダイアログを呼ぶ', () => {
    const confirmSpy = vi.spyOn(window, 'confirm').mockReturnValue(true);
    const actions = setup(['trash']);
    const action = actions.find((a) => a.id === 'trash')!;
    const { container } = render(<>{action.renderForm!([])}</>);
    const button = container.querySelector('button')!;
    expect(button).toHaveClass('acms-admin-btn-admin', 'acms-admin-btn-admin-danger');
    button.click();
    expect(confirmSpy).toHaveBeenCalled();
    confirmSpy.mockRestore();
  });

  describe('ctx.dataview.registerBulkAction("entry", ...) レジストリとのマージ', () => {
    afterEach(() => {
      getDataviewBulkActionRegistry().remove('entry', 'plugin-bulk-action');
    });

    it('レジストリ未登録（ハンドラ0件）ならコア定義のみが getBulkActions に渡る（特徴付けテスト）', () => {
      const context = createContext([]);
      let received: string[] | undefined;
      renderHook(() =>
        useEntryAdminBulkActions({
          getBulkActions: (actions) => {
            received = actions.map((a) => a.id);
            return actions;
          },
          context,
        })
      );
      expect(received).toEqual(['order', 'status', 'user', 'category', 'blog', 'duplicate', 'export', 'trash']);
    });

    it('レジストリに登録した一括アクションがコア定義の後ろに追加され getBulkActions に渡る', () => {
      getDataviewBulkActionRegistry().add('entry', 'plugin-bulk-action', {
        id: 'plugin-bulk-action',
        label: 'プラグイン一括操作',
      });

      // setup() は getBulkActions: (actions) => actions のパススルーのため、
      // レジストリ登録内容がそのまま最終結果に含まれることを確認する
      const actions = setup([]);
      expect(actions.map((a) => a.id)).toContain('plugin-bulk-action');
      expect(actions.at(-1)?.id).toBe('plugin-bulk-action');
    });

    it('レジストリ登録後もレガシー getBulkActions（フィルタ）が最終結果に適用される', () => {
      getDataviewBulkActionRegistry().add('entry', 'plugin-bulk-action', {
        id: 'plugin-bulk-action',
        label: 'プラグイン一括操作',
      });
      const context = createContext([]);
      const { result } = renderHook(() =>
        useEntryAdminBulkActions({
          getBulkActions: (actions) => actions.filter((a) => a.id !== 'plugin-bulk-action'),
          context,
        })
      );
      expect(result.current.map((a) => a.id)).not.toContain('plugin-bulk-action');
    });
  });

  const callOnAction = async (actionId: string, formData: FormData) => {
    const actions = setup([]);
    const action = actions.find((a) => a.id === actionId)!;
    await act(async () => {
      await (action.onAction as unknown as (formData: FormData, data: unknown[]) => Promise<void>)(formData, []);
    });
  };

  describe('entry.bulk-status.before / entry.bulk-status.success', () => {
    afterEach(() => {
      const emit = window.ACMS.events.emit as ReturnType<typeof vi.fn>;
      emit.mockClear();
      emit.mockImplementation(async (_type: string, payload: unknown) => ({ canceled: false, event: payload }));
      vi.mocked(bulkChangeStatus).mockClear();
    });

    it('bulkChangeStatus の前後で entry.bulk-status.before → entry.bulk-status.success の順に発火する', async () => {
      const formData = new FormData();

      await callOnAction('status', formData);

      const emit = window.ACMS.events.emit as ReturnType<typeof vi.fn>;
      expect(emit).toHaveBeenNthCalledWith(1, 'entry.bulk-status.before', { formData });
      expect(bulkChangeStatus).toHaveBeenCalledWith(formData);
      expect(emit).toHaveBeenNthCalledWith(2, 'entry.bulk-status.success', { formData });
    });

    it('entry.bulk-status.before がキャンセルされた場合は bulkChangeStatus を呼ばない', async () => {
      (window.ACMS.events.emit as ReturnType<typeof vi.fn>).mockImplementation(async () => ({
        canceled: true,
        reason: 'blocked',
        event: {},
      }));

      await callOnAction('status', new FormData());

      expect(bulkChangeStatus).not.toHaveBeenCalled();
    });
  });

  describe('entry.bulk-duplicate.success / entry.bulk-trash.success', () => {
    afterEach(() => {
      (window.ACMS.events.emit as ReturnType<typeof vi.fn>).mockClear();
      vi.mocked(bulkDuplicate).mockClear();
      vi.mocked(bulkTrash).mockClear();
    });

    it('bulkDuplicate 成功後に entry.bulk-duplicate.success を発火する', async () => {
      const formData = new FormData();

      await callOnAction('duplicate', formData);

      expect(bulkDuplicate).toHaveBeenCalledWith(formData);
      expect(window.ACMS.events.emit).toHaveBeenCalledWith('entry.bulk-duplicate.success', { formData });
    });

    it('bulkTrash 成功後に entry.bulk-trash.success を発火する', async () => {
      const formData = new FormData();

      await callOnAction('trash', formData);

      expect(bulkTrash).toHaveBeenCalledWith(formData);
      expect(window.ACMS.events.emit).toHaveBeenCalledWith('entry.bulk-trash.success', { formData });
    });
  });
});
