import { describe, test, expect, vi, afterEach } from 'vitest';
import React from 'react';
import { createRoot, Root } from 'react-dom/client';
import { act } from 'react-dom/test-utils';

/**
 * Closes the gap flagged by the whole-branch review: PublishWarnings.test.tsx
 * covers the presentational banner in isolation, but nothing asserted that
 * `result.warnings` from `publish()` actually reaches it through TopBar. That
 * 3-line seam (handlePublish -> setPublishWarnings -> <PublishWarnings>) is
 * exactly what regressed before this feature existed: the backend has always
 * returned `warnings`, and TopBar discarded them by only checking
 * `result.success` -- so the contact-form-relay warning was dead code for its
 * entire life. This suite drives the real `<TopBar>` through a real button
 * click and asserts the warnings show up, survive the 3s "Published" flash,
 * don't taint the success/error status, and get cleared by a fresh publish.
 *
 * Mocks (same DOM-harness + `vi.mock('@craftjs/core', ...)` pattern as
 * RenderNode.test.tsx / useWhpApi.load.test.tsx -- no @testing-library/react
 * in this repo):
 *  - `@craftjs/core`'s `useEditor`: TopBar only needs inert undo/redo/query
 *    stubs, not a real Craft.js tree.
 *  - `useWhpApi`: this IS the seam under test -- `publish` is a controllable
 *    mock so each test can choose exactly what the "backend" returns.
 *  - TemplateModal / HeadCodeModal / SitesmithButton: sibling chrome
 *    unrelated to the warnings seam (portals, CodeMirror lazy-load,
 *    useSitesmith's own fetch calls) -- stubbed out so this suite stays
 *    focused, same as MobilePanelBar.test.tsx stubbing its sibling panels.
 * `usePages`/`useSiteDesign`/`useMobileChrome` are left un-mocked and
 * un-provided -- their default context values (defined in each context
 * module) are harmless no-op stubs, and TopBar's desktop render path never
 * needs more than that.
 */

vi.mock('@craftjs/core', () => ({
  useEditor: (collector?: (state: unknown, query: unknown) => Record<string, unknown>) => {
    const query = {
      serialize: () => '{}',
      history: { canUndo: () => false, canRedo: () => false },
    };
    const actions = { history: { undo: vi.fn(), redo: vi.fn() } };
    const collected = collector ? collector({}, query) : {};
    return { actions, query, ...collected };
  },
}));

const publishMock = vi.fn();
vi.mock('../../hooks/useWhpApi', () => ({
  useWhpApi: () => ({
    save: vi.fn().mockResolvedValue({ success: true }),
    publish: publishMock,
    load: vi.fn().mockResolvedValue(null),
    uploadAsset: vi.fn(),
    isWHP: true,
  }),
}));

vi.mock('./TemplateModal', () => ({ TemplateModal: () => null }));
vi.mock('./HeadCodeModal', () => ({ HeadCodeModal: () => null }));
vi.mock('../sitesmith/SitesmithButton', () => ({ SitesmithButton: () => null }));

import { TopBar } from './TopBar';
import { EditorConfigProvider } from '../../state/EditorConfigContext';
import { WhpConfig } from '../../types';

const whpConfig: WhpConfig = {
  user: 'testuser',
  apiUrl: '/panel/api/site-builder',
  csrfToken: 'tok',
  siteId: 1,
  siteDomain: 'example.com',
  siteName: 'Example Site',
  backUrl: '/panel/sites',
  isRoot: false,
};

let container: HTMLDivElement;
let root: Root;

function render() {
  container = document.createElement('div');
  document.body.appendChild(container);
  act(() => {
    root = createRoot(container);
    root.render(
      <EditorConfigProvider config={whpConfig}>
        <TopBar device="desktop" onDeviceChange={() => {}} showGuides={false} onToggleGuides={() => {}} />
      </EditorConfigProvider>,
    );
  });
}

function publishButton(): HTMLButtonElement {
  const btn = container.querySelector<HTMLButtonElement>('.topbar-btn.publish');
  if (!btn) throw new Error('Publish button not found');
  return btn;
}

/** handlePublish does exactly one `await publish()` before touching state;
 *  two microtask flushes inside the same act() batch is enough to carry that
 *  through to the resulting re-render. */
async function clickPublish() {
  await act(async () => {
    publishButton().dispatchEvent(new MouseEvent('click', { bubbles: true }));
    await Promise.resolve();
    await Promise.resolve();
  });
}

afterEach(() => {
  if (container) {
    act(() => { root.unmount(); });
    container.remove();
  }
  publishMock.mockReset();
  vi.useRealTimers();
});

describe('TopBar publish-warnings wiring', () => {
  test('warnings from publish() flow through into the rendered banner', async () => {
    publishMock.mockResolvedValue({ success: true, warnings: ['Warning one.', 'Warning two.'] });
    render();

    await clickPublish();

    expect(container.textContent).toContain('Warning one.');
    expect(container.textContent).toContain('Warning two.');
  });

  test('a clean publish (no warnings key) renders no banner at all', async () => {
    publishMock.mockResolvedValue({ success: true });
    render();

    await clickPublish();

    expect(container.querySelector('[data-testid="publish-warnings"]')).toBeNull();
  });

  test('a warning is non-blocking -- publish still reports success, not a failure', async () => {
    publishMock.mockResolvedValue({
      success: true,
      warnings: ['Submissions will not be delivered until an administrator enables it.'],
    });
    render();

    await clickPublish();

    expect(container.querySelector('.publish-badge.published')).not.toBeNull();
    expect(container.querySelector('.save-indicator.error')).toBeNull();
    expect(container.textContent).toContain('Submissions will not be delivered until an administrator enables it.');
  });

  test('warnings survive the 3-second "Published" flash timer', async () => {
    vi.useFakeTimers();
    publishMock.mockResolvedValue({ success: true, warnings: ['Sticks around after the flash.'] });
    render();

    await act(async () => {
      publishButton().dispatchEvent(new MouseEvent('click', { bubbles: true }));
      await Promise.resolve();
      await Promise.resolve();
    });

    // Sanity: both the flash and the warning are up before the timer fires.
    expect(container.querySelector('.publish-badge.published')).not.toBeNull();
    expect(container.textContent).toContain('Sticks around after the flash.');

    act(() => {
      vi.advanceTimersByTime(3000);
    });

    // The 3s timer resets publishStatus -> the "Published" flash is gone...
    expect(container.querySelector('.publish-badge.published')).toBeNull();
    // ...but publishWarnings lives in its own state and must NOT have been
    // cleared by that same timer.
    expect(container.textContent).toContain('Sticks around after the flash.');
  });

  test('a new publish attempt clears warnings left over from the previous one', async () => {
    publishMock.mockResolvedValueOnce({ success: true, warnings: ['Old warning.'] });
    render();
    await clickPublish();
    expect(container.textContent).toContain('Old warning.');

    publishMock.mockResolvedValueOnce({ success: true });
    await clickPublish();

    expect(container.textContent).not.toContain('Old warning.');
    expect(container.querySelector('[data-testid="publish-warnings"]')).toBeNull();
  });
});
