import { bootstrap } from "@/app";
import { AppModule } from "@/app.module";
import { PrismaModule } from "@/modules/prisma/prisma.module";
import { StripeService } from "@/modules/stripe/stripe.service";
import { TokensModule } from "@/modules/tokens/tokens.module";
import { UsersModule } from "@/modules/users/users.module";
import { UserWithProfile } from "@/modules/users/users.repository";
import { INestApplication } from "@nestjs/common";
import { NestExpressApplication } from "@nestjs/platform-express";
import { Test } from "@nestjs/testing";
import Stripe from "stripe";
import * as request from "supertest";
import { PlatformBillingRepositoryFixture } from "test/fixtures/repository/billing.repository.fixture";
import { MembershipRepositoryFixture } from "test/fixtures/repository/membership.repository.fixture";
import { OAuthClientRepositoryFixture } from "test/fixtures/repository/oauth-client.repository.fixture";
import { OrganizationRepositoryFixture } from "test/fixtures/repository/organization.repository.fixture";
import { ProfileRepositoryFixture } from "test/fixtures/repository/profiles.repository.fixture";
import { UserRepositoryFixture } from "test/fixtures/repository/users.repository.fixture";
import { withApiAuth } from "test/utils/withApiAuth";

import { Team, PlatformOAuthClient, PlatformBilling } from "@calcom/prisma/client";

describe("Platform Billing Controller (e2e)", () => {
  let app: INestApplication;
  const userEmail = "platform-billing-controller-e2e@api.com";
  let user: UserWithProfile;
  let billing: PlatformBilling;
  let userRepositoryFixture: UserRepositoryFixture;
  let organizationsRepositoryFixture: OrganizationRepositoryFixture;
  let profileRepositoryFixture: ProfileRepositoryFixture;
  let membershipsRepositoryFixture: MembershipRepositoryFixture;
  let oauthClientRepositoryFixture: OAuthClientRepositoryFixture;
  let oAuthClient: PlatformOAuthClient;
  let platformBillingRepositoryFixture: PlatformBillingRepositoryFixture;
  let organization: Team;

  beforeAll(async () => {
    const moduleRef = await withApiAuth(
      userEmail,
      Test.createTestingModule({
        imports: [AppModule, PrismaModule, UsersModule, TokensModule],
      })
    ).compile();
    userRepositoryFixture = new UserRepositoryFixture(moduleRef);
    organizationsRepositoryFixture = new OrganizationRepositoryFixture(moduleRef);
    profileRepositoryFixture = new ProfileRepositoryFixture(moduleRef);
    oauthClientRepositoryFixture = new OAuthClientRepositoryFixture(moduleRef);
    membershipsRepositoryFixture = new MembershipRepositoryFixture(moduleRef);
    platformBillingRepositoryFixture = new PlatformBillingRepositoryFixture(moduleRef);
    organization = await organizationsRepositoryFixture.create({ name: "platform billing" });

    user = await userRepositoryFixture.create({
      email: userEmail,
      username: userEmail,
    });

    await profileRepositoryFixture.create({
      uid: `usr-${user.id}`,
      username: userEmail,
      organization: {
        connect: {
          id: organization.id,
        },
      },
      user: {
        connect: {
          id: user.id,
        },
      },
    });
    await membershipsRepositoryFixture.create({
      role: "OWNER",
      team: { connect: { id: organization.id } },
      user: { connect: { id: user.id } },
      accepted: true,
    });

    billing = await platformBillingRepositoryFixture.create(organization.id);

    app = moduleRef.createNestApplication();
    bootstrap(app as NestExpressApplication);

    await app.init();
  });

  afterAll(async () => {
    userRepositoryFixture.deleteByEmail(user.email);
    await app.close();
  });

  it("/billing/webhook (POST) should set billing free plan for org", () => {
    jest.spyOn(StripeService.prototype, "getStripe").mockImplementation(
      () =>
        ({
          webhooks: {
            constructEventAsync: async () => {
              return {
                type: "checkout.session.completed",
                data: {
                  object: {
                    metadata: {
                      teamId: organization.id,
                      plan: "FREE",
                    },
                    mode: "subscription",
                  },
                },
              };
            },
          },
        } as unknown as Stripe)
    );

    return request(app.getHttpServer())
      .post("/v2/billing/webhook")
      .expect(200)
      .then(async (res) => {
        const billing = await platformBillingRepositoryFixture.get(organization.id);
        expect(billing?.plan).toEqual("FREE");
      });
  });
  it("/billing/webhook (POST) failed payment should set billing free plan to overdue", () => {
    jest.spyOn(StripeService.prototype, "getStripe").mockImplementation(
      () =>
        ({
          webhooks: {
            constructEventAsync: async () => {
              return {
                type: "invoice.payment_failed",
                data: {
                  object: {
                    customer: billing?.customerId,
                    subscription: billing?.subscriptionId,
                  },
                },
              };
            },
          },
        } as unknown as Stripe)
    );

    return request(app.getHttpServer())
      .post("/v2/billing/webhook")
      .expect(200)
      .then(async (res) => {
        const billing = await platformBillingRepositoryFixture.get(organization.id);
        expect(billing?.overdue).toEqual(true);
      });
  });

  it("/billing/webhook (POST) success payment should set billing free plan to not overdue", () => {
    jest.spyOn(StripeService.prototype, "getStripe").mockImplementation(
      () =>
        ({
          webhooks: {
            constructEventAsync: async () => {
              return {
                type: "invoice.payment_succeeded",
                data: {
                  object: {
                    customer: billing?.customerId,
                    subscription: billing?.subscriptionId,
                  },
                },
              };
            },
          },
        } as unknown as Stripe)
    );

    return request(app.getHttpServer())
      .post("/v2/billing/webhook")
      .expect(200)
      .then(async (res) => {
        const billing = await platformBillingRepositoryFixture.get(organization.id);
        expect(billing?.overdue).toEqual(false);
      });
  });

  it("/billing/webhook (POST) should delete subscription", () => {
    jest.spyOn(StripeService.prototype, "getStripe").mockImplementation(
      () =>
        ({
          webhooks: {
            constructEventAsync: async () => {
              return {
                type: "customer.subscription.deleted",
                data: {
                  object: {
                    metadata: {
                      teamId: organization.id,
                      plan: "FREE",
                    },
                    id: billing?.subscriptionId,
                  },
                },
              };
            },
          },
        } as unknown as Stripe)
    );

    return request(app.getHttpServer())
      .post("/v2/billing/webhook")
      .expect(200)
      .then(async (res) => {
        const billing = await platformBillingRepositoryFixture.get(organization.id);
        expect(billing).toBeNull();
      });
  });
});
