// @ts-ignore
import {
  Controller,
  Get,
  INestApplication,
  BadRequestException,
  Injectable,
  InternalServerErrorException,
} from "@nestjs/common";
import { Test, TestingModule } from "@nestjs/testing";
import * as request from "supertest";
import { TracingModule } from "../../../lib";
import { XRAY_CLIENT } from "../../../lib/core/constants";
import { XRayClient } from "../../../lib/core/interfaces";
import { segmentEmitterMock } from "../../util/segmentEmitterMock";
import { Segment } from "aws-xray-sdk";
import { HttpEnvController } from "./http-env.controller";
import { HttpEnvService } from "./http-env.service";

describe("HttpEnvironment (e2e)", () => {
  let app: INestApplication;

  let controller: HttpEnvController;
  let service: HttpEnvService;
  let xrayClient: XRayClient;
  let sendSegment: jest.SpyInstance<Segment>;

  beforeEach(async () => {
    const moduleFixture: TestingModule = await Test.createTestingModule({
      imports: [
        TracingModule.forRoot({
          serviceName: "http-env-e2e-test",
          // EC2 plugin crashes in Github Actions
          plugins: [],
        }),
      ],
      providers: [HttpEnvService],
      controllers: [HttpEnvController],
    }).compile();

    app = moduleFixture.createNestApplication();
    app.enableShutdownHooks();
    await app.init();

    controller = app.get<HttpEnvController>(HttpEnvController);
    service = app.get<HttpEnvService>(HttpEnvService);
    xrayClient = app.get<XRayClient>(XRAY_CLIENT);

    sendSegment = segmentEmitterMock();
  });

  afterEach(async () => {
    await app.close();
    sendSegment.mockRestore();
  });

  it("should be defined", () => {
    expect(controller).toBeDefined();
    expect(service).toBeDefined();
    expect(xrayClient).toBeDefined();
  });

  it("should send a segment for a successful request", async () => {
    await request(app.getHttpServer())
      .get("/url")
      .expect(200)
      .expect("HttpEnv");

    expect(sendSegment).toHaveBeenCalledTimes(1);
    expect(sendSegment).toHaveBeenLastCalledWith(
      expect.objectContaining({
        name: "http-env-e2e-test",
        service: expect.objectContaining({ name: "@narando/nest-xray" }),
        http: expect.objectContaining({
          request: expect.objectContaining({
            client_ip: "::ffff:127.0.0.1",
            method: "GET",
            url: expect.stringMatching(/^http:\/\/127.0.0.1:[0-9]{1,5}\/url/),
            user_agent: expect.stringMatching(/^node-superagent/),
          }),
          response: expect.objectContaining({
            status: 200,
          }),
        }),
        subsegments: expect.arrayContaining([
          expect.objectContaining({ name: "http-env-e2e-test-subsegment" }),
        ]),
      })
    );
  });

  it("should correctly parse the request url when using query parameters", async () => {
    // Regression test for #140
    await request(app.getHttpServer()).get("/url?id=1234").expect(200);

    expect(sendSegment).toHaveBeenCalledTimes(1);

    const submittedURL = sendSegment.mock.calls[0][0].http.request.url;

    expect(submittedURL).toMatch(/^http:\/\/127.0.0.1:[0-9]{1,5}\/url$/);
  });

  it("should send a segment for a 4xx error", async () => {
    service.index = jest.fn().mockRejectedValue(new BadRequestException());

    await request(app.getHttpServer()).get("/url").expect(400);

    expect(sendSegment).toHaveBeenCalledTimes(1);
    expect(sendSegment).toHaveBeenLastCalledWith(
      expect.objectContaining({
        http: expect.objectContaining({
          request: expect.objectContaining({
            method: "GET",
            url: expect.stringMatching(/^http:\/\/127.0.0.1:[0-9]{1,5}\/url/),
          }),
          response: expect.objectContaining({
            status: 400,
          }),
        }),
      })
    );
  });

  it("should send a segment for a 5xx error", async () => {
    service.index = jest
      .fn()
      .mockRejectedValue(new InternalServerErrorException());

    await request(app.getHttpServer()).get("/url").expect(500);

    expect(sendSegment).toHaveBeenCalledTimes(1);
    expect(sendSegment).toHaveBeenLastCalledWith(
      expect.objectContaining({
        http: expect.objectContaining({
          request: expect.objectContaining({
            method: "GET",
            url: expect.stringMatching(/^http:\/\/127.0.0.1:[0-9]{1,5}\/url/),
          }),
          response: expect.objectContaining({
            status: 500,
          }),
        }),
      })
    );
  });
});