/**
 * Copyright (c) Microsoft Corporation.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import fs from 'fs';
import path from 'path';

import { test, expect, parseResponse } from './fixtures';

test('browser_network_requests', async ({ client, server }) => {
  server.setContent('/', `
    <button onclick="fetch('/json')">Click me</button>
    <img src="/image.png" />
  `, 'text/html');

  server.setContent('/json', JSON.stringify({ name: 'John Doe' }), 'application/json');

  await client.callTool({
    name: 'browser_navigate',
    arguments: {
      url: server.PREFIX,
    },
  });

  await client.callTool({
    name: 'browser_click',
    arguments: {
      element: 'Click me button',
      target: 'e2',
    },
  });

  {
    const response = parseResponse(await client.callTool({
      name: 'browser_network_requests',
    }));
    expect(response.result).not.toContain(`[GET] ${`${server.PREFIX}/`} => [200] OK`);
    expect(response.result).toMatch(new RegExp(String.raw`^\d+\. \[GET\] ${escapeRegExp(`${server.PREFIX}/json`)} => \[200\] OK$`, 'm'));
    expect(response.result).toMatch(new RegExp(String.raw`^\d+\. \[GET\] ${escapeRegExp(`${server.PREFIX}/image.png`)} => \[404\]`, 'm'));
    expect(response.result).toContain('Note: 1 static request not shown, run with "static" option to see it.');
  }

  {
    const response = parseResponse(await client.callTool({
      name: 'browser_network_requests',
      arguments: {
        static: true,
      },
    }));
    expect(response.result).toMatch(new RegExp(String.raw`^\d+\. \[GET\] ${escapeRegExp(`${server.PREFIX}/`)} => \[200\] OK$`, 'm'));
    expect(response.result).toMatch(new RegExp(String.raw`^\d+\. \[GET\] ${escapeRegExp(`${server.PREFIX}/json`)} => \[200\] OK$`, 'm'));
    expect(response.result).toMatch(new RegExp(String.raw`^\d+\. \[GET\] ${escapeRegExp(`${server.PREFIX}/image.png`)} => \[404\]`, 'm'));
    expect(response.result).not.toContain('not shown');
  }
});

test('browser_network_requests filter', async ({ client, server }) => {
  server.setContent('/', '', 'text/html');

  await client.callTool({
    name: 'browser_navigate',
    arguments: { url: server.PREFIX },
  });

  await client.callTool({
    name: 'browser_evaluate',
    arguments: {
      function: `async () => {
        await Promise.all([fetch('/api/users'), fetch('/api/orders'), fetch('/static/image.png')]);
      }`,
    },
  });

  {
    const response = parseResponse(await client.callTool({
      name: 'browser_network_requests',
      arguments: { filter: '/api/', static: true },
    }));
    expect(response.result).toContain(`${server.PREFIX}/api/users`);
    expect(response.result).toContain(`${server.PREFIX}/api/orders`);
    expect(response.result).not.toContain(`${server.PREFIX}/static/image.png`);
  }
});

test('browser_network_requests numbers requests with stable indexes', async ({ client, server }) => {
  server.setContent('/', '', 'text/html');

  await client.callTool({
    name: 'browser_navigate',
    arguments: { url: server.PREFIX },
  });

  await client.callTool({
    name: 'browser_evaluate',
    arguments: {
      function: `async () => {
        await fetch('/api/users');
        await fetch('/api/orders');
      }`,
    },
  });

  // Index assignment is stable across calls — the same request keeps the same number.
  const response = parseResponse(await client.callTool({
    name: 'browser_network_requests',
    arguments: { static: true },
  }));
  const lines = response.result.split('\n').filter(Boolean);
  expect(lines[0]).toMatch(/^1\. \[GET\] /);
  expect(lines).toHaveLength(3);
});

test('browser_network_request shows full request and response details', async ({ client, server }) => {
  server.setContent('/', `
    <button onclick="fetch('/api', { method: 'POST', headers: { 'X-Custom-Header': 'test-value' }, body: JSON.stringify({ key: 'value' }) })">Click me</button>
  `, 'text/html');
  server.setRoute('/api', (_req, res) => {
    res.setHeader('X-Custom-Response', 'response-value');
    res.setHeader('Content-Type', 'application/json');
    res.end(JSON.stringify({ name: 'John Doe' }));
  });

  await client.callTool({
    name: 'browser_navigate',
    arguments: { url: server.PREFIX },
  });

  await client.callTool({
    name: 'browser_click',
    arguments: { element: 'Click me button', target: 'e2' },
  });

  // Find the index of the /api request from the listing.
  const list = parseResponse(await client.callTool({
    name: 'browser_network_requests',
  }));
  const match = list.result.match(/^(\d+)\. \[POST\] [^ ]+\/api =>/m);
  expect(match).not.toBeNull();
  const index = Number(match![1]);

  const detail = parseResponse(await client.callTool({
    name: 'browser_network_request',
    arguments: { index },
  }));
  expect(detail.result).toContain(`#${index} [POST] ${server.PREFIX}/api`);
  expect(detail.result).toContain('General');
  expect(detail.result).toContain('status:    [200] OK');
  expect(detail.result).toContain('mimeType:  application/json');
  expect(detail.result).toContain('Request headers');
  expect(detail.result).toContain('x-custom-header: test-value');
  expect(detail.result).toContain('Response headers');
  expect(detail.result).toContain('x-custom-response: response-value');
  expect(detail.result).toContain('Call browser_network_request with part="request-body" to read the request body.');
  expect(detail.result).toContain('Call browser_network_request with part="response-body" to read the response body.');
  expect(detail.result).not.toContain('Request body');
  expect(detail.result).not.toContain('Response body');
  expect(detail.result).not.toContain('{"key":"value"}');
});

test('browser_network_request reports failed requests', async ({ client, server }) => {
  server.setContent('/', `<img src="/missing.png" />`, 'text/html');

  await client.callTool({
    name: 'browser_navigate',
    arguments: { url: server.PREFIX },
  });

  const list = parseResponse(await client.callTool({
    name: 'browser_network_requests',
    arguments: { static: true },
  }));
  const match = list!.result!.match(/^(\d+)\. \[GET\] [^ ]+\/missing\.png =>/m);
  expect(match).not.toBeNull();

  const detail = parseResponse(await client.callTool({
    name: 'browser_network_request',
    arguments: { index: Number(match![1]) },
  }));
  expect(detail!.result).toContain(`#${match![1]} [GET] ${server.PREFIX}/missing.png`);
  expect(detail!.result).toContain('status:    [404]');
});

test('browser_network_request returns individual parts', async ({ client, server }) => {
  server.setContent('/', `
    <button onclick="fetch('/api', { method: 'POST', headers: { 'X-Custom-Header': 'test-value' }, body: JSON.stringify({ key: 'value' }) })">Click me</button>
  `, 'text/html');
  server.setRoute('/api', (_req, res) => {
    res.setHeader('X-Custom-Response', 'response-value');
    res.setHeader('Content-Type', 'application/json');
    res.end(JSON.stringify({ name: 'John Doe' }));
  });

  await client.callTool({
    name: 'browser_navigate',
    arguments: { url: server.PREFIX },
  });

  await client.callTool({
    name: 'browser_click',
    arguments: { element: 'Click me button', target: 'e2' },
  });

  const list = parseResponse(await client.callTool({
    name: 'browser_network_requests',
  }));
  const match = list!.result!.match(/^(\d+)\. \[POST\] [^ ]+\/api =>/m);
  expect(match).not.toBeNull();
  const index = Number(match![1]);

  const requestHeaders = parseResponse(await client.callTool({
    name: 'browser_network_request',
    arguments: { index, part: 'request-headers' },
  }));
  expect(requestHeaders!.result).toContain('x-custom-header: test-value');
  expect(requestHeaders!.result).not.toContain('General');

  const requestBody = parseResponse(await client.callTool({
    name: 'browser_network_request',
    arguments: { index, part: 'request-body' },
  }));
  expect(requestBody!.result).toBe('{"key":"value"}');

  const responseHeaders = parseResponse(await client.callTool({
    name: 'browser_network_request',
    arguments: { index, part: 'response-headers' },
  }));
  expect(responseHeaders!.result).toContain('x-custom-response: response-value');

  const responseBody = parseResponse(await client.callTool({
    name: 'browser_network_request',
    arguments: { index, part: 'response-body' },
  }));
  expect(responseBody!.result).toBe('{"name":"John Doe"}');
});

test('browser_network_request response-body part saves binary to a file', async ({ client, server }) => {
  const pngBytes = Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]);
  server.setContent('/', `
    <button onclick="fetch('/image.png')">Click me</button>
  `, 'text/html');
  server.setRoute('/image.png', (_req, res) => {
    res.setHeader('Content-Type', 'image/png');
    res.end(pngBytes);
  });

  await client.callTool({
    name: 'browser_navigate',
    arguments: { url: server.PREFIX },
  });

  await client.callTool({
    name: 'browser_click',
    arguments: { element: 'Click me button', target: 'e2' },
  });

  const list = parseResponse(await client.callTool({
    name: 'browser_network_requests',
    arguments: { static: true },
  }));
  const match = list!.result!.match(/^(\d+)\. \[GET\] [^ ]+\/image\.png =>/m);
  expect(match).not.toBeNull();

  const detail = parseResponse(await client.callTool({
    name: 'browser_network_request',
    arguments: { index: Number(match![1]), part: 'response-body' },
  }));
  const bodyPath = path.resolve(test.info().outputPath(), detail!.result!.trim());
  expect(fs.readFileSync(bodyPath)).toEqual(pngBytes);
});

test('browser_network_request rejects out-of-range index', async ({ client, server }) => {
  await client.callTool({
    name: 'browser_navigate',
    arguments: { url: server.PREFIX },
  });

  const response = await client.callTool({
    name: 'browser_network_request',
    arguments: { index: 999 },
  });
  const parsed = parseResponse(response);
  expect(parsed!.error).toContain('Request #999 not found');
});

function escapeRegExp(text: string): string {
  return text.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
}
