// Copyright 2023 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import * as Common from '../../core/common/common.js';
import * as Platform from '../../core/platform/platform.js';
import * as SDK from '../../core/sdk/sdk.js';
import * as Logs from '../../models/logs/logs.js';
import * as TextUtils from '../../models/text_utils/text_utils.js';
import * as Workspace from '../../models/workspace/workspace.js';
import {describeWithLocale} from '../../testing/EnvironmentHelpers.js';
import type * as Search from '../search/search.js';

import * as Network from './network.js';

const {urlString} = Platform.DevToolsPath;

describeWithLocale('NetworkSearchScope', () => {
  let scope: Network.NetworkSearchScope.NetworkSearchScope;

  beforeEach(() => {
    const fakeRequest1 = SDK.NetworkRequest.NetworkRequest.createWithoutBackendRequest(
        'fakeId1', urlString`http://example.com/main.js`, urlString`http://example.com/index.html`, null);
    fakeRequest1.setRequestHeaders([{name: 'fooRequestHeader', value: 'value1'}]);
    fakeRequest1.responseHeaders = [{name: 'fooResponseHeader', value: 'foo value'}];
    fakeRequest1.setResourceType(Common.ResourceType.resourceTypes.Script);
    fakeRequest1.mimeType = 'text/javascript';
    fakeRequest1.setContentDataProvider(
        async () => new TextUtils.ContentData.ContentData(
            'This is the response body of request 1.\nAnd a second line.\n', false, fakeRequest1.mimeType));

    const fakeRequest2 = SDK.NetworkRequest.NetworkRequest.createWithoutBackendRequest(
        'fakeId1', urlString`http://example.com/bundle.min.js`, urlString`http://example.com/index.html`, null);
    fakeRequest2.setRequestHeaders([{name: 'barRequestHeader', value: 'value2'}]);
    fakeRequest2.responseHeaders = [{name: 'barResponseHeader', value: 'bar value'}];
    fakeRequest2.setResourceType(Common.ResourceType.resourceTypes.Script);
    fakeRequest2.mimeType = 'text/javascript';
    fakeRequest2.setContentDataProvider(
        async () => new TextUtils.ContentData.ContentData(
            'This is the first line.\nAnd another line in the response body of request 2.\n', false,
            fakeRequest2.mimeType));

    const fakeLog = sinon.createStubInstance(Logs.NetworkLog.NetworkLog, {requests: [fakeRequest1, fakeRequest2]});
    scope = new Network.NetworkSearchScope.NetworkSearchScope(fakeLog);
  });

  it('calls finished callback when done', async () => {
    const searchConfig = new Workspace.SearchConfig.SearchConfig('foo', false, false);
    const finishedStub = sinon.stub();

    await scope.performSearch(searchConfig, new Common.Progress.Progress(), () => {}, finishedStub);

    assert.isTrue(finishedStub.calledOnceWith(true));
  });

  it('finds requests by url', async () => {
    const searchConfig = new Workspace.SearchConfig.SearchConfig('main', false, false);
    const results: Search.SearchScope.SearchResult[] = [];

    await scope.performSearch(searchConfig, new Common.Progress.Progress(), result => results.push(result), () => {});

    assert.lengthOf(results, 1);
    assert.strictEqual(results[0].label(), 'main.js');
    assert.strictEqual(results[0].matchesCount(), 1);
    assert.strictEqual(results[0].matchLabel(0), 'URL');
    assert.strictEqual(results[0].matchLineContent(0), 'http://example.com/main.js');
  });

  it('finds request header names', async () => {
    const searchConfig = new Workspace.SearchConfig.SearchConfig('fooRequest', false, false);
    const results: Search.SearchScope.SearchResult[] = [];

    await scope.performSearch(searchConfig, new Common.Progress.Progress(), result => results.push(result), () => {});

    assert.lengthOf(results, 1);
    assert.strictEqual(results[0].label(), 'main.js');
    assert.strictEqual(results[0].matchesCount(), 1);
    assert.strictEqual(results[0].matchLabel(0), 'fooRequestHeader:');
    assert.strictEqual(results[0].matchLineContent(0), 'value1');
  });

  it('finds request header values', async () => {
    const searchConfig = new Workspace.SearchConfig.SearchConfig('value2', false, false);
    const results: Search.SearchScope.SearchResult[] = [];

    await scope.performSearch(searchConfig, new Common.Progress.Progress(), result => results.push(result), () => {});

    assert.lengthOf(results, 1);
    assert.strictEqual(results[0].label(), 'bundle.min.js');
    assert.strictEqual(results[0].matchesCount(), 1);
    assert.strictEqual(results[0].matchLabel(0), 'barRequestHeader:');
    assert.strictEqual(results[0].matchLineContent(0), 'value2');
  });

  it('finds response header names', async () => {
    const searchConfig = new Workspace.SearchConfig.SearchConfig('barResponse', false, false);
    const results: Search.SearchScope.SearchResult[] = [];

    await scope.performSearch(searchConfig, new Common.Progress.Progress(), result => results.push(result), () => {});

    assert.lengthOf(results, 1);
    assert.strictEqual(results[0].label(), 'bundle.min.js');
    assert.strictEqual(results[0].matchesCount(), 1);
    assert.strictEqual(results[0].matchLabel(0), 'barResponseHeader:');
    assert.strictEqual(results[0].matchLineContent(0), 'bar value');
  });

  it('finds response header values', async () => {
    const searchConfig = new Workspace.SearchConfig.SearchConfig('foo value', false, false);
    const results: Search.SearchScope.SearchResult[] = [];

    await scope.performSearch(searchConfig, new Common.Progress.Progress(), result => results.push(result), () => {});

    assert.lengthOf(results, 1);
    assert.strictEqual(results[0].label(), 'main.js');
    assert.strictEqual(results[0].matchesCount(), 1);
    assert.strictEqual(results[0].matchLabel(0), 'fooResponseHeader:');
    assert.strictEqual(results[0].matchLineContent(0), 'foo value');
  });

  it('honors "file:" query prefixes to filter requests', async () => {
    const searchConfig = new Workspace.SearchConfig.SearchConfig('f:main.js value', false, false);
    const results: Search.SearchScope.SearchResult[] = [];

    await scope.performSearch(searchConfig, new Common.Progress.Progress(), result => results.push(result), () => {});

    assert.lengthOf(results, 1);
    assert.strictEqual(results[0].label(), 'main.js');
    assert.strictEqual(results[0].matchesCount(), 2);

    assert.strictEqual(results[0].matchLabel(0), 'fooRequestHeader:');
    assert.strictEqual(results[0].matchLineContent(0), 'value1');
    assert.strictEqual(results[0].matchLabel(1), 'fooResponseHeader:');
    assert.strictEqual(results[0].matchLineContent(1), 'foo value');
  });

  it('finds matches in response bodies', async () => {
    const searchConfig = new Workspace.SearchConfig.SearchConfig('response body', false, false);
    const results: Search.SearchScope.SearchResult[] = [];

    await scope.performSearch(searchConfig, new Common.Progress.Progress(), result => results.push(result), () => {});

    assert.lengthOf(results, 2);
    assert.strictEqual(results[0].label(), 'bundle.min.js');
    assert.strictEqual(results[0].matchesCount(), 1);
    assert.strictEqual(results[0].matchLabel(0), '2');
    assert.strictEqual(results[0].matchLineContent(0), 'And another line in the response body of request 2.');

    assert.strictEqual(results[1].label(), 'main.js');
    assert.strictEqual(results[1].matchesCount(), 1);
    assert.strictEqual(results[1].matchLabel(0), '1');
    assert.strictEqual(results[1].matchLineContent(0), 'This is the response body of request 1.');
  });

  it('handles "file:" prefix correctly for response body matches', async () => {
    const searchConfig = new Workspace.SearchConfig.SearchConfig('f:bundle.min.js response body', false, false);
    const results: Search.SearchScope.SearchResult[] = [];

    await scope.performSearch(searchConfig, new Common.Progress.Progress(), result => results.push(result), () => {});

    assert.lengthOf(results, 1);
    assert.strictEqual(results[0].label(), 'bundle.min.js');
    assert.strictEqual(results[0].matchesCount(), 1);
    assert.strictEqual(results[0].matchLabel(0), '2');
    assert.strictEqual(results[0].matchLineContent(0), 'And another line in the response body of request 2.');
  });

  it('finds matches in response bodies only if all parts of a query match', async () => {
    const searchConfig = new Workspace.SearchConfig.SearchConfig('"response body""second line"', false, false);
    const results: Search.SearchScope.SearchResult[] = [];

    await scope.performSearch(searchConfig, new Common.Progress.Progress(), result => results.push(result), () => {});

    assert.lengthOf(results, 1);
    assert.strictEqual(results[0].label(), 'main.js');
    assert.strictEqual(results[0].matchesCount(), 2);
    assert.strictEqual(results[0].matchLabel(0), '1');
    assert.strictEqual(results[0].matchLineContent(0), 'This is the response body of request 1.');
    assert.strictEqual(results[0].matchLabel(1), '2');
    assert.strictEqual(results[0].matchLineContent(1), 'And a second line.');
  });
});
