import { buildSchema, parse } from 'graphql';
import nock from 'nock';
import { Logger, MemoryLogWriter } from '@graphql-hive/logger';
import { createHive } from '../src/client/client';
import { atLeastOnceSampler } from '../src/client/samplers';
import type { Report } from '../src/client/usage';
import { version } from '../src/version';
import {
  createHiveTestingLogger,
  fastFetchError,
  normalizeLogMessage,
  waitFor,
  waitUntil,
} from './test-utils';

const headers = {
  'Content-Type': 'application/json',
  'graphql-client-name': 'Hive Client',
  'graphql-client-version': version,
};

const schema = buildSchema(/* GraphQL */ `
  type Query {
    project(selector: ProjectSelectorInput!): Project
    projectsByType(type: ProjectType!): [Project!]!
    projects(filter: FilterInput): [Project!]!
  }

  type Mutation {
    deleteProject(selector: ProjectSelectorInput!): DeleteProjectPayload!
  }

  input ProjectSelectorInput {
    organization: ID!
    project: ID!
  }

  input FilterInput {
    type: ProjectType
    pagination: PaginationInput
  }

  input PaginationInput {
    limit: Int
    offset: Int
  }

  type ProjectSelector {
    organization: ID!
    project: ID!
  }

  type DeleteProjectPayload {
    selector: ProjectSelector!
    deletedProject: Project!
  }

  type Project {
    id: ID!
    cleanId: ID!
    name: String!
    type: ProjectType!
    buildUrl: String
    validationUrl: String
  }

  enum ProjectType {
    FEDERATION
    STITCHING
    SINGLE
    CUSTOM
  }
`);

const op = parse(/* GraphQL */ `
  mutation deleteProject($selector: ProjectSelectorInput!) {
    deleteProject(selector: $selector) {
      selector {
        organization
        project
      }
      deletedProject {
        ...ProjectFields
      }
    }
  }

  fragment ProjectFields on Project {
    id
    cleanId
    name
    type
  }
`);

const op2 = parse(/* GraphQL */ `
  query getProject($selector: ProjectSelectorInput!) {
    project(selector: $selector) {
      ...ProjectFields
    }
  }

  fragment ProjectFields on Project {
    id
    cleanId
    name
    type
  }
`);

beforeEach(() => {
  vi.restoreAllMocks();
});

afterEach(() => {
  nock.cleanAll();
});

test('should send data to Hive', async () => {
  const logger = createHiveTestingLogger();

  const token = 'Token';

  let report: Report = {
    size: 0,
    map: {},
    operations: [],
  };
  const http = nock('http://localhost')
    .post('/200')
    .matchHeader('Authorization', `Bearer ${token}`)
    .matchHeader('Content-Type', headers['Content-Type'])
    .matchHeader('graphql-client-name', headers['graphql-client-name'])
    .matchHeader('graphql-client-version', headers['graphql-client-version'])
    .once()
    .reply((_, _body) => {
      report = _body as any;
      return [200];
    });

  const hive = createHive({
    enabled: true,
    debug: true,
    agent: {
      timeout: 500,
      maxRetries: 0,
      sendInterval: 10,
      logger,
    },
    token,
    selfHosting: {
      graphqlEndpoint: 'http://localhost/graphql',
      applicationUrl: 'http://localhost/',
      usageEndpoint: 'http://localhost/200',
    },
    usage: true,
  });

  const collect = hive.collectUsage();

  await waitFor(20);
  await collect(
    {
      schema,
      document: op,
      operationName: 'deleteProject',
    },
    {},
  );
  await hive.dispose();
  await waitFor(40);
  http.done();

  expect(logger.getLogs()).toMatchInlineSnapshot(`
    [DBG] Disposing
    [DBG] Sending report (queue 1)
    [DBG] POST http://localhost/200 (x-request-id=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx)
    [DBG] POST http://localhost/200 (x-request-id=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx) succeeded with status 200 (666ms).
    [DBG] Report sent!
  `);

  // Map
  expect(report.size).toEqual(1);
  expect(Object.keys(report.map)).toHaveLength(1);

  const key = Object.keys(report.map)[0];
  const record = report.map[key];

  // operation
  expect(record.operation).toMatch('mutation deleteProject');
  expect(record.operationName).toMatch('deleteProject');
  // fields
  expect(record.fields).toMatchInlineSnapshot(`
    [
      Mutation.deleteProject,
      Mutation.deleteProject.selector,
      DeleteProjectPayload.selector,
      ProjectSelector.organization,
      ProjectSelector.project,
      DeleteProjectPayload.deletedProject,
      Project.id,
      Project.cleanId,
      Project.name,
      Project.type,
      ProjectType.FEDERATION,
      ProjectType.STITCHING,
      ProjectType.SINGLE,
      ProjectType.CUSTOM,
      ProjectSelectorInput.organization,
      ID,
      ProjectSelectorInput.project,
    ]
  `);

  // Operations
  const operations = report.operations;
  expect(operations).toHaveLength(1); // one operation
  if (!operations?.length) {
    throw new Error('Expected operations to be an array');
  }

  const operation = operations[0];

  expect(operation.operationMapKey).toEqual(key);
  expect(operation.timestamp).toEqual(expect.any(Number));
  // execution
  expect(operation.execution.duration).toBeGreaterThanOrEqual(18 * 1_000_000); // >=18ms in microseconds
  expect(operation.execution.duration).toBeLessThan(25 * 1_000_000); // <25ms
  expect(operation.execution.errorsTotal).toBe(0);
  expect(operation.execution.ok).toBe(true);
});

test('should send data to Hive (deprecated endpoint)', async () => {
  const logger = createHiveTestingLogger();
  const token = 'Token';

  let report: Report = {
    size: 0,
    map: {},
    operations: [],
  };
  const http = nock('http://localhost')
    .post('/200')
    .matchHeader('Authorization', `Bearer ${token}`)
    .matchHeader('Content-Type', headers['Content-Type'])
    .matchHeader('graphql-client-name', headers['graphql-client-name'])
    .matchHeader('graphql-client-version', headers['graphql-client-version'])
    .once()
    .reply((_, _body) => {
      report = _body as any;
      return [200];
    });

  const hive = createHive({
    enabled: true,
    debug: true,
    agent: {
      timeout: 500,
      maxRetries: 0,
      sendInterval: 10,
      logger,
    },
    token,
    usage: {
      endpoint: 'http://localhost/200',
    },
  });

  const collect = hive.collectUsage();

  await waitFor(20);
  await collect(
    {
      schema,
      document: op,
      operationName: 'deleteProject',
    },
    {},
  );
  await hive.dispose();
  await waitFor(50);
  http.done();

  expect(logger.getLogs()).toMatchInlineSnapshot(`
    [DBG] Disposing
    [DBG] Sending report (queue 1)
    [DBG] POST http://localhost/200 (x-request-id=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx)
    [DBG] POST http://localhost/200 (x-request-id=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx) succeeded with status 200 (666ms).
    [DBG] Report sent!
  `);

  // Map
  expect(report.size).toEqual(1);
  expect(Object.keys(report.map)).toHaveLength(1);

  const key = Object.keys(report.map)[0];
  const record = report.map[key];

  // operation
  expect(record.operation).toMatch('mutation deleteProject');
  expect(record.operationName).toMatch('deleteProject');
  // fields
  expect(record.fields).toMatchInlineSnapshot(`
    [
      Mutation.deleteProject,
      Mutation.deleteProject.selector,
      DeleteProjectPayload.selector,
      ProjectSelector.organization,
      ProjectSelector.project,
      DeleteProjectPayload.deletedProject,
      Project.id,
      Project.cleanId,
      Project.name,
      Project.type,
      ProjectType.FEDERATION,
      ProjectType.STITCHING,
      ProjectType.SINGLE,
      ProjectType.CUSTOM,
      ProjectSelectorInput.organization,
      ID,
      ProjectSelectorInput.project,
    ]
  `);

  // Operations
  const operations = report.operations;
  expect(operations).toHaveLength(1); // one operation
  if (!operations?.length) {
    throw new Error('Expected operations to be an array');
  }

  const operation = operations[0];

  expect(operation.operationMapKey).toEqual(key);
  expect(operation.timestamp).toEqual(expect.any(Number));
  // execution
  expect(operation.execution.duration).toBeGreaterThanOrEqual(18 * 1_000_000); // >=18ms in microseconds
  expect(operation.execution.duration).toBeLessThan(25 * 1_000_000); // <25ms
  expect(operation.execution.errorsTotal).toBe(0);
  expect(operation.execution.ok).toBe(true);
});

test('should not leak the exception', { retry: 3 }, async () => {
  const logger = createHiveTestingLogger();

  const hive = createHive({
    enabled: true,
    debug: true,
    agent: {
      fetch: fastFetchError,
      timeout: 500,
      maxRetries: 1,
      sendInterval: 10,
      minTimeout: 10,
      logger,
    },
    token: 'Token',
    usage: {
      endpoint: 'http://404.localhost.noop',
    },
  });

  await hive.collectUsage()(
    {
      schema,
      document: op,
      operationName: 'deleteProject',
    },
    {},
  );

  await waitFor(50);
  await hive.dispose();

  expect(logger.getLogs()).toMatchInlineSnapshot(`
    [DBG] Sending report (queue 1)
    [DBG] POST http://404.localhost.noop (x-request-id=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx) Attempt (1/2)
    [DBG] Error: getaddrinfo ENOTFOUND 404.localhost.noop
    [DBG] POST http://404.localhost.noop (x-request-id=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx) failed (666ms). getaddrinfo ENOTFOUND 404.localhost.noop
    [DBG] Disposing
  `);
});

test('sendImmediately should not stop the schedule', async () => {
  const logger = createHiveTestingLogger();

  const token = 'Token';

  const http = nock('http://localhost')
    .post('/200')
    .matchHeader('authorization', `Bearer ${token}`)
    .matchHeader('Content-Type', headers['Content-Type'])
    .matchHeader('graphql-client-name', headers['graphql-client-name'])
    .matchHeader('graphql-client-version', headers['graphql-client-version'])
    .times(3)
    .reply((_, _body) => {
      return [200];
    });

  const hive = createHive({
    enabled: true,
    debug: true,
    printTokenInfo: false,
    agent: {
      timeout: 500,
      maxRetries: 0,
      maxSize: 2,
      minTimeout: 10,
      logger,
      sendInterval: 50,
    },
    token,
    usage: {
      endpoint: 'http://localhost/200',
    },
  });

  const args = {
    schema,
    document: op,
    operationName: 'deleteProject',
  };

  const collect = hive.collectUsage();

  await collect(
    {
      schema,
      document: op,
      operationName: 'deleteProject',
    },
    {},
  );
  await waitFor(120);
  // Because maxSize is 2 and sendInterval is 50ms (+120ms buffer)
  // the scheduled send task should be done by now

  // since we sent only 1 element, the buffer was not full,
  // so we should not see "Sending immediately"

  expect(logger.getLogs()).not.toMatch('Sending immediately');
  logger.clear();

  // Now we will hit the maxSize
  // We run collect two times
  await Promise.all([collect(args, {}), collect(args, {})]);
  await waitUntil(() => logger.getLogs().includes('Sending immediately'));
  expect(logger.getLogs()).toMatch('Sending report (queue 2)');
  logger.clear();
  // Let's check if the scheduled send task is still running
  await collect(args, {});
  await waitFor(60);
  expect(logger.getLogs()).toMatch('Sending report (queue 1)');

  await hive.dispose();
  http.done();
});

test('should send data to Hive at least once when using atLeastOnceSampler', async () => {
  const logger = createHiveTestingLogger();

  const token = 'Token';

  let report: Report = {
    size: 0,
    map: {},
    operations: [],
  };
  const http = nock('http://localhost')
    .post('/200')
    .matchHeader('Authorization', `Bearer ${token}`)
    .matchHeader('Content-Type', headers['Content-Type'])
    .matchHeader('graphql-client-name', headers['graphql-client-name'])
    .matchHeader('graphql-client-version', headers['graphql-client-version'])
    .once()
    .reply((_, _body) => {
      report = _body as any;
      return [200];
    });

  const hive = createHive({
    enabled: true,
    debug: true,
    printTokenInfo: false,
    agent: {
      timeout: 500,
      sendInterval: 10,
      maxRetries: 0,
      logger,
    },
    token,
    selfHosting: {
      graphqlEndpoint: 'http://localhost/graphql',
      applicationUrl: 'http://localhost/',
      usageEndpoint: 'http://localhost/200',
    },
    usage: {
      sampler: atLeastOnceSampler({
        keyFn(ctx) {
          return ctx.operationName;
        },
        sampler() {
          // only
          return 0;
        },
      }),
    },
  });

  const collect = hive.collectUsage();

  await Promise.all([
    collect(
      {
        schema,
        document: op,
        operationName: 'deleteProject',
      },
      {},
    ),
    // different query
    collect(
      {
        schema,
        document: op2,
        operationName: 'getProject',
      },
      {},
    ),
    // duplicated call
    collect(
      {
        schema,
        document: op,
        operationName: 'deleteProject',
      },
      {},
    ),
  ]);
  await hive.dispose();
  await waitUntil(() => logger.getLogs().includes('Report sent!'));
  http.done();

  expect(logger.getLogs()).toMatch(/\[DBG\] Disposing/);
  expect(logger.getLogs()).toMatch(/\[DBG\] Sending report \(queue 2\)/);
  expect(logger.getLogs()).toMatch(/\[DBG\] POST http:\/\/localhost\/200/);
  expect(logger.getLogs()).toMatch(/succeeded with status 200/);
  expect(logger.getLogs()).toMatch(/\[DBG\] Report sent!/);

  // Map
  expect(report.size).toEqual(2);
  expect(Object.keys(report.map)).toHaveLength(2);

  const foundRecords: string[] = [];
  for (const key in report.map) {
    const record = report.map[key];

    foundRecords.push(record.operationName ?? 'anonymous');
  }

  expect(foundRecords).toContainEqual('deleteProject');
  expect(foundRecords).toContainEqual('getProject');

  const operations = report.operations;
  expect(operations).toHaveLength(2); // two operations
});

test('should not send excluded operation name data to Hive', async () => {
  const logger = createHiveTestingLogger();

  const token = 'Token';

  let report: Report = {
    size: 0,
    map: {},
    operations: [],
  };
  const http = nock('http://localhost')
    .post('/200')
    .once()
    .reply((_, _body) => {
      report = _body as any;
      return [200];
    });

  const hive = createHive({
    enabled: true,
    debug: true,
    agent: {
      timeout: 500,
      maxRetries: 0,
      sendInterval: 10,
      logger,
    },
    token,
    selfHosting: {
      graphqlEndpoint: 'http://localhost/graphql',
      applicationUrl: 'http://localhost/',
      usageEndpoint: 'http://localhost/200',
    },
    usage: {
      exclude: ['deleteProjectShouldntBeIncluded', new RegExp('ExcludeThis$')],
    },
  });

  const collect = hive.collectUsage();
  await waitFor(20);
  await Promise.all([
    (collect(
      {
        schema,
        document: op,
        operationName: 'deleteProjectExcludeThis',
      },
      {},
    ),
    collect(
      {
        schema,
        document: op,
        operationName: 'deleteProjectShouldntBeIncluded',
      },
      {},
    ),
    collect(
      {
        schema,
        document: op,
        operationName: 'deleteProject',
      },
      {},
    ),
    collect(
      {
        schema,
        document: op2,
        operationName: 'getProject',
      },
      {},
    )),
  ]);
  await hive.dispose();
  await waitFor(50);
  http.done();

  expect(logger.getLogs()).toMatchInlineSnapshot(`
    [DBG] Disposing
    [DBG] Sending report (queue 2)
    [DBG] POST http://localhost/200 (x-request-id=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx)
    [DBG] POST http://localhost/200 (x-request-id=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx) succeeded with status 200 (666ms).
    [DBG] Report sent!
  `);

  // Map
  expect(report.size).toEqual(2);
  expect(Object.keys(report.map)).toHaveLength(2);

  const key = Object.keys(report.map)[0];
  const record = report.map[key];

  // operation
  expect(record.operation).toMatch('mutation deleteProject');
  expect(record.operationName).toMatch('deleteProject');
  // fields
  expect(record.fields).toMatchInlineSnapshot(`
    [
      Mutation.deleteProject,
      Mutation.deleteProject.selector,
      DeleteProjectPayload.selector,
      ProjectSelector.organization,
      ProjectSelector.project,
      DeleteProjectPayload.deletedProject,
      Project.id,
      Project.cleanId,
      Project.name,
      Project.type,
      ProjectType.FEDERATION,
      ProjectType.STITCHING,
      ProjectType.SINGLE,
      ProjectType.CUSTOM,
      ProjectSelectorInput.organization,
      ID,
      ProjectSelectorInput.project,
    ]
  `);

  // Operations
  const operations = report.operations;
  expect(operations).toHaveLength(2); // two operations
  if (!operations?.length) {
    throw new Error('Expected operations to be an array');
  }

  const operation = operations[0];

  expect(operation.operationMapKey).toEqual(key);
  expect(operation.timestamp).toEqual(expect.any(Number));
  // execution
  expect(operation.execution.duration).toBeGreaterThanOrEqual(18 * 1_000_000); // >=18ms in microseconds
  expect(operation.execution.duration).toBeLessThan(25 * 1_000_000); // <25ms
  expect(operation.execution.errorsTotal).toBe(0);
  expect(operation.execution.ok).toBe(true);
});

test('retry on non-200', async () => {
  const logger = createHiveTestingLogger();

  const token = 'Token';

  const fetchSpy = vi.fn(async (_url: RequestInfo | URL, _init?: RequestInit) => {
    return new Response('No no no', { status: 500, statusText: 'Internal server error' });
  });

  const hive = createHive({
    enabled: true,
    debug: true,
    printTokenInfo: false,
    agent: {
      logger,
      timeout: 10,
      minTimeout: 10,
      sendInterval: 10,
      maxRetries: 1,
      fetch: fetchSpy,
    },
    token,
    usage: {
      endpoint: 'http://localhost/200',
    },
    reporting: false,
  });

  const collect = hive.collectUsage();

  await collect(
    {
      schema,
      document: op,
      operationName: 'asd',
    },
    {},
  );

  await waitFor(50);
  await hive.dispose();

  expect(logger.getLogs()).toMatchInlineSnapshot(`
    [DBG] Sending report (queue 1)
    [DBG] POST http://localhost/200 (x-request-id=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx) Attempt (1/2)
    [DBG] POST http://localhost/200 (x-request-id=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx) failed with status 500 (666ms): No no no
    [DBG] Disposing
  `);
});

test('constructs URL with usage.target (hvo1/)', async ({ expect }) => {
  const logger = createHiveTestingLogger();
  const token = 'hvo1/brrrrt';
  const dUrl = Promise.withResolvers<string>();

  const hive = createHive({
    enabled: true,
    debug: true,
    agent: {
      timeout: 500,
      maxRetries: 0,
      sendInterval: 1,
      maxSize: 1,
      async fetch(url) {
        dUrl.resolve(url.toString());
        return new Response('', {
          status: 200,
        });
      },
      logger,
    },
    token,
    selfHosting: {
      graphqlEndpoint: 'http://localhost:2/graphql',
      applicationUrl: 'http://localhost:1',
      usageEndpoint: 'http://localhost',
    },
    usage: {
      target: 'the-guild/graphql-hive/staging',
    },
  });

  await hive.collectUsage()(
    {
      schema,
      document: op,
      operationName: 'asd',
    },
    {},
  );

  const url = await dUrl.promise;
  expect(url).toEqual('http://localhost/the-guild/graphql-hive/staging');
  await hive.dispose();
});

test('constructs URL with usage.target (hvp1/)', async ({ expect }) => {
  const logger = createHiveTestingLogger();
  const token = 'hvp1/brrrrt';
  const dUrl = Promise.withResolvers<string>();

  const hive = createHive({
    enabled: true,
    debug: true,
    agent: {
      timeout: 500,
      maxRetries: 0,
      sendInterval: 1,
      maxSize: 1,
      async fetch(url) {
        dUrl.resolve(url.toString());
        return new Response('', {
          status: 200,
        });
      },
      logger,
    },
    token,
    selfHosting: {
      graphqlEndpoint: 'http://localhost:2/graphql',
      applicationUrl: 'http://localhost:1',
      usageEndpoint: 'http://localhost',
    },
    usage: {
      target: 'the-guild/graphql-hive/staging',
    },
  });

  await hive.collectUsage()(
    {
      schema,
      document: op,
      operationName: 'asd',
    },
    {},
  );

  const url = await dUrl.promise;
  expect(url).toEqual('http://localhost/the-guild/graphql-hive/staging');
  await hive.dispose();
});

test('constructs URL with usage.target (hvu1/)', async ({ expect }) => {
  const logger = createHiveTestingLogger();
  const token = 'hvu1/brrrrt';
  const dUrl = Promise.withResolvers<string>();

  const hive = createHive({
    enabled: true,
    debug: true,
    agent: {
      timeout: 500,
      maxRetries: 0,
      sendInterval: 1,
      maxSize: 1,
      async fetch(url) {
        dUrl.resolve(url.toString());
        return new Response('', {
          status: 200,
        });
      },
      logger,
    },
    token,
    selfHosting: {
      graphqlEndpoint: 'http://localhost:2/graphql',
      applicationUrl: 'http://localhost:1',
      usageEndpoint: 'http://localhost',
    },
    usage: {
      target: 'the-guild/graphql-hive/staging',
    },
  });

  await hive.collectUsage()(
    {
      schema,
      document: op,
      operationName: 'asd',
    },
    {},
  );

  const url = await dUrl.promise;
  expect(url).toEqual('http://localhost/the-guild/graphql-hive/staging');
  await hive.dispose();
});

test('no debug property -> logger.debug is invoked', async ({ expect }) => {
  const logger = createHiveTestingLogger();
  const token = 'hvu1/brrrrt';

  const hive = createHive({
    enabled: true,
    agent: {
      timeout: 500,
      maxRetries: 0,
      sendInterval: 1,
      maxSize: 1,
      async fetch() {
        return new Response('', {
          status: 200,
        });
      },
      logger,
    },
    token,
    selfHosting: {
      graphqlEndpoint: 'http://localhost:2/graphql',
      applicationUrl: 'http://localhost:1',
      usageEndpoint: 'http://localhost',
    },
    usage: {
      target: 'the-guild/graphql-hive/staging',
    },
  });

  await hive.collectUsage()(
    {
      schema,
      document: op,
      operationName: 'asd',
    },
    {},
  );

  await hive.dispose();
  expect(logger.getLogs()).toMatchInlineSnapshot(`
    [DBG] Disposing
    [DBG] Sending immediately
    [DBG] Sending report (queue 1)
    [DBG] POST http://localhost/the-guild/graphql-hive/staging (x-request-id=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx)
    [DBG] POST http://localhost/the-guild/graphql-hive/staging (x-request-id=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx) succeeded with status 200 (666ms).
    [DBG] Report sent!
  `);
});

test('debug: false -> logger.debug is not invoked', async ({ expect }) => {
  const logger = createHiveTestingLogger();
  const token = 'hvu1/brrrrt';

  const hive = createHive({
    enabled: true,
    debug: false,
    agent: {
      timeout: 500,
      maxRetries: 0,
      sendInterval: 1,
      maxSize: 1,
      async fetch() {
        return new Response('', {
          status: 200,
        });
      },
      logger,
    },
    token,
    selfHosting: {
      graphqlEndpoint: 'http://localhost:2/graphql',
      applicationUrl: 'http://localhost:1',
      usageEndpoint: 'http://localhost',
    },
    usage: {
      target: 'the-guild/graphql-hive/staging',
    },
  });

  await hive.collectUsage()(
    {
      schema,
      document: op,
      operationName: 'asd',
    },
    {},
  );

  await hive.dispose();
  expect(logger.getLogs()).toMatchInlineSnapshot(`
    [DBG] Disposing
    [DBG] Sending immediately
    [DBG] Sending report (queue 1)
    [DBG] POST http://localhost/the-guild/graphql-hive/staging (x-request-id=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx)
    [DBG] POST http://localhost/the-guild/graphql-hive/staging (x-request-id=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx) succeeded with status 200 (666ms).
    [DBG] Report sent!
  `);
});

test('debug: true and missing logger.debug method -> logger.info is invoked (to cover legacy logger implementation)', async ({
  expect,
}) => {
  const logger = createHiveTestingLogger();
  // @ts-expect-error we remove this property to emulate logger without it
  logger.debug = undefined;
  const token = 'hvu1/brrrrt';

  const hive = createHive({
    enabled: true,
    debug: true,
    agent: {
      timeout: 500,
      maxRetries: 0,
      sendInterval: 1,
      maxSize: 1,
      async fetch() {
        return new Response('', {
          status: 200,
        });
      },
      logger,
    },
    token,
    selfHosting: {
      graphqlEndpoint: 'http://localhost:2/graphql',
      applicationUrl: 'http://localhost:1',
      usageEndpoint: 'http://localhost',
    },
    usage: {
      target: 'the-guild/graphql-hive/staging',
    },
  });

  await hive.collectUsage()(
    {
      schema,
      document: op,
      operationName: 'asd',
    },
    {},
  );

  await hive.dispose();
  expect(logger.getLogs()).toMatchInlineSnapshot(`
    [INF] Disposing
    [INF] Sending immediately
    [INF] Sending report (queue 1)
    [INF] POST http://localhost/the-guild/graphql-hive/staging (x-request-id=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx)
    [INF] POST http://localhost/the-guild/graphql-hive/staging (x-request-id=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx) succeeded with status 200 (666ms).
    [INF] Report sent!
  `);
});

test('new logger option', async () => {
  const logWriter = new MemoryLogWriter();
  const logger = new Logger({
    writers: [
      {
        write(level, attrs, msg) {
          if (msg) {
            msg = normalizeLogMessage(msg);
          }

          logWriter.write(level, attrs, msg);
        },
      },
    ],
    level: 'debug',
  });

  const token = 'hvu1/brrrrt';

  const hive = createHive({
    enabled: true,
    logger,
    agent: {
      timeout: 500,
      maxRetries: 0,
      sendInterval: 1,
      maxSize: 1,
      async fetch() {
        return new Response('', {
          status: 200,
        });
      },
    },
    token,
    selfHosting: {
      graphqlEndpoint: 'http://localhost:2/graphql',
      applicationUrl: 'http://localhost:1',
      usageEndpoint: 'http://localhost',
    },
    usage: {
      target: 'the-guild/graphql-hive/staging',
    },
  });

  await hive.collectUsage()(
    {
      schema,
      document: op,
      operationName: 'asd',
    },
    {},
  );

  await hive.dispose();
  expect(logWriter.logs).toMatchInlineSnapshot(`
    [
      {
        attrs: {
          module: hive-agent,
        },
        level: debug,
        msg: Disposing,
      },
      {
        attrs: {
          module: hive-agent,
        },
        level: debug,
        msg: Sending immediately,
      },
      {
        attrs: {
          module: hive-agent,
        },
        level: debug,
        msg: Sending report (queue 1),
      },
      {
        attrs: {
          module: hive-agent,
        },
        level: debug,
        msg: POST http://localhost/the-guild/graphql-hive/staging (x-request-id=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx),
      },
      {
        attrs: {
          module: hive-agent,
        },
        level: debug,
        msg: POST http://localhost/the-guild/graphql-hive/staging (x-request-id=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx) succeeded with status 200 (666ms).,
      },
      {
        attrs: {
          module: hive-agent,
        },
        level: debug,
        msg: Report sent!,
      },
    ]
  `);
});
