/**
 * @license
 * Copyright 2025 Google LLC
 * SPDX-License-Identifier: Apache-2.0
 */

import assert from 'node:assert';
import {describe, it} from 'node:test';

import type {Dialog} from 'puppeteer-core';

import {
  listPages,
  newPage,
  closePage,
  selectPage,
  navigatePage,
  resizePage,
  handleDialog,
} from '../../src/tools/pages.js';
import {withMcpContext} from '../utils.js';

describe('pages', () => {
  describe('list_pages', () => {
    it('list pages', async () => {
      await withMcpContext(async (response, context) => {
        await listPages.handler({params: {}}, response, context);
        assert.ok(response.includePages);
      });
    });
  });
  describe('new_page', () => {
    it('create a page', async () => {
      await withMcpContext(async (response, context) => {
        assert.strictEqual(context.getPageByIdx(0), context.getSelectedPage());
        await newPage.handler(
          {params: {url: 'about:blank'}},
          response,
          context,
        );
        assert.strictEqual(context.getPageByIdx(1), context.getSelectedPage());
        assert.ok(response.includePages);
      });
    });
  });
  describe('close_page', () => {
    it('closes a page', async () => {
      await withMcpContext(async (response, context) => {
        const page = await context.newPage();
        assert.strictEqual(context.getPageByIdx(1), context.getSelectedPage());
        assert.strictEqual(context.getPageByIdx(1), page);
        await closePage.handler({params: {pageIdx: 1}}, response, context);
        assert.ok(page.isClosed());
        assert.ok(response.includePages);
      });
    });
    it('cannot close the last page', async () => {
      await withMcpContext(async (response, context) => {
        const page = context.getSelectedPage();
        await closePage.handler({params: {pageIdx: 0}}, response, context);
        assert.deepStrictEqual(
          response.responseLines[0],
          `The last open page cannot be closed. It is fine to keep it open.`,
        );
        assert.ok(response.includePages);
        assert.ok(!page.isClosed());
      });
    });
  });
  describe('select_page', () => {
    it('selects a page', async () => {
      await withMcpContext(async (response, context) => {
        await context.newPage();
        assert.strictEqual(context.getPageByIdx(1), context.getSelectedPage());
        await selectPage.handler({params: {pageIdx: 0}}, response, context);
        assert.strictEqual(context.getPageByIdx(0), context.getSelectedPage());
        assert.ok(response.includePages);
      });
    });
    it('selects a page and keeps it focused in the background', async () => {
      await withMcpContext(async (response, context) => {
        await context.newPage();
        assert.strictEqual(context.getPageByIdx(1), context.getSelectedPage());
        assert.strictEqual(
          await context.getPageByIdx(0).evaluate(() => document.hasFocus()),
          false,
        );
        await selectPage.handler({params: {pageIdx: 0}}, response, context);
        assert.strictEqual(context.getPageByIdx(0), context.getSelectedPage());
        assert.strictEqual(
          await context.getPageByIdx(0).evaluate(() => document.hasFocus()),
          true,
        );
        assert.ok(response.includePages);
      });
    });
  });
  describe('navigate_page', () => {
    it('navigates to correct page', async () => {
      await withMcpContext(async (response, context) => {
        await navigatePage.handler(
          {params: {url: 'data:text/html,<div>Hello MCP</div>'}},
          response,
          context,
        );
        const page = context.getSelectedPage();
        assert.equal(
          await page.evaluate(() => document.querySelector('div')?.textContent),
          'Hello MCP',
        );
        assert.ok(response.includePages);
      });
    });

    it('throws an error if the page was closed not by the MCP server', async () => {
      await withMcpContext(async (response, context) => {
        const page = await context.newPage();
        assert.strictEqual(context.getPageByIdx(1), context.getSelectedPage());
        assert.strictEqual(context.getPageByIdx(1), page);

        await page.close();

        try {
          await navigatePage.handler(
            {params: {url: 'data:text/html,<div>Hello MCP</div>'}},
            response,
            context,
          );
          assert.fail('should not reach here');
        } catch (err) {
          assert.strictEqual(
            err.message,
            'The selected page has been closed. Call list_pages to see open pages.',
          );
        }
      });
    });
    it('go back', async () => {
      await withMcpContext(async (response, context) => {
        const page = context.getSelectedPage();
        await page.goto('data:text/html,<div>Hello MCP</div>');
        await navigatePage.handler({params: {type: 'back'}}, response, context);

        assert.equal(
          await page.evaluate(() => document.location.href),
          'about:blank',
        );
        assert.ok(response.includePages);
      });
    });
    it('go forward', async () => {
      await withMcpContext(async (response, context) => {
        const page = context.getSelectedPage();
        await page.goto('data:text/html,<div>Hello MCP</div>');
        await page.goBack();
        await navigatePage.handler(
          {params: {type: 'forward'}},
          response,
          context,
        );

        assert.equal(
          await page.evaluate(() => document.querySelector('div')?.textContent),
          'Hello MCP',
        );
        assert.ok(response.includePages);
      });
    });
    it('reload', async () => {
      await withMcpContext(async (response, context) => {
        const page = context.getSelectedPage();
        await page.goto('data:text/html,<div>Hello MCP</div>');
        await navigatePage.handler(
          {params: {type: 'reload'}},
          response,
          context,
        );

        assert.equal(
          await page.evaluate(() => document.location.href),
          'data:text/html,<div>Hello MCP</div>',
        );
        assert.ok(response.includePages);
      });
    });
    it('go forward with error', async () => {
      await withMcpContext(async (response, context) => {
        await navigatePage.handler(
          {params: {type: 'forward'}},
          response,
          context,
        );

        assert.ok(
          response.responseLines
            .at(0)
            ?.startsWith('Unable to navigate forward in the selected page:'),
        );
        assert.ok(response.includePages);
      });
    });
    it('go back with error', async () => {
      await withMcpContext(async (response, context) => {
        await navigatePage.handler({params: {type: 'back'}}, response, context);

        assert.ok(
          response.responseLines
            .at(0)
            ?.startsWith('Unable to navigate back in the selected page:'),
        );
        assert.ok(response.includePages);
      });
    });
  });
  describe('resize', () => {
    it('create a page', async () => {
      await withMcpContext(async (response, context) => {
        const page = context.getSelectedPage();
        const resizePromise = page.evaluate(() => {
          return new Promise(resolve => {
            window.addEventListener('resize', resolve, {once: true});
          });
        });
        await resizePage.handler(
          {params: {width: 700, height: 500}},
          response,
          context,
        );
        await resizePromise;
        const dimensions = await page.evaluate(() => {
          return [window.innerWidth, window.innerHeight];
        });
        assert.deepStrictEqual(dimensions, [700, 500]);
      });
    });
  });

  describe('dialogs', () => {
    it('can accept dialogs', async () => {
      await withMcpContext(async (response, context) => {
        const page = context.getSelectedPage();
        const dialogPromise = new Promise<void>(resolve => {
          page.on('dialog', () => {
            resolve();
          });
        });
        page.evaluate(() => {
          alert('test');
        });
        await dialogPromise;
        await handleDialog.handler(
          {
            params: {
              action: 'accept',
            },
          },
          response,
          context,
        );
        assert.strictEqual(context.getDialog(), undefined);
        assert.strictEqual(
          response.responseLines[0],
          'Successfully accepted the dialog',
        );
      });
    });
    it('can dismiss dialogs', async () => {
      await withMcpContext(async (response, context) => {
        const page = context.getSelectedPage();
        const dialogPromise = new Promise<void>(resolve => {
          page.on('dialog', () => {
            resolve();
          });
        });
        page.evaluate(() => {
          alert('test');
        });
        await dialogPromise;
        await handleDialog.handler(
          {
            params: {
              action: 'dismiss',
            },
          },
          response,
          context,
        );
        assert.strictEqual(context.getDialog(), undefined);
        assert.strictEqual(
          response.responseLines[0],
          'Successfully dismissed the dialog',
        );
      });
    });
    it('can dismiss already dismissed dialog dialogs', async () => {
      await withMcpContext(async (response, context) => {
        const page = context.getSelectedPage();
        const dialogPromise = new Promise<Dialog>(resolve => {
          page.on('dialog', dialog => {
            resolve(dialog);
          });
        });
        page.evaluate(() => {
          alert('test');
        });
        const dialog = await dialogPromise;
        await dialog.dismiss();
        await handleDialog.handler(
          {
            params: {
              action: 'dismiss',
            },
          },
          response,
          context,
        );
        assert.strictEqual(context.getDialog(), undefined);
        assert.strictEqual(
          response.responseLines[0],
          'Successfully dismissed the dialog',
        );
      });
    });
  });
});
