import { stripe } from "@better-auth/stripe";
import { apiKey, genericOAuth } from "better-auth/plugins";
import { magicLink } from "better-auth/plugins/magic-link";

import type { dbClient } from "@kan/db/client";
import * as memberRepo from "@kan/db/repository/member.repo";
import * as subscriptionRepo from "@kan/db/repository/subscription.repo";
import * as userRepo from "@kan/db/repository/user.repo";
import * as workspaceRepo from "@kan/db/repository/workspace.repo";
import { sendEmail } from "@kan/email";
import { createLogger } from "@kan/logger";
import { generateUID } from "@kan/shared/utils";
import { createStripeClient } from "@kan/stripe";

import { socialProvidersPlugin } from "./providers";
import { triggerWorkflow } from "./utils";

const log = createLogger("auth");

async function cancelWorkspaceAccess(
  db: dbClient,
  workspacePublicId: string,
): Promise<void> {
  const workspace = await workspaceRepo.getByPublicId(db, workspacePublicId);

  if (!workspace) return;

  const preserveUserId = await memberRepo.getPreservableMemberId(
    db,
    workspace.id,
    workspace.createdBy ?? null,
  );

  let newSlug = workspace.publicId;
  if (workspace.slug !== workspace.publicId) {
    const isPublicIdAvailable = await workspaceRepo.isWorkspaceSlugAvailable(
      db,
      workspace.publicId,
    );
    if (!isPublicIdAvailable) {
      newSlug = generateUID();
    }
  }

  await Promise.all([
    preserveUserId
      ? memberRepo.pauseMembersExcept(db, workspace.id, preserveUserId)
      : memberRepo.pauseAllMembers(db, workspace.id),
    workspaceRepo.update(db, workspacePublicId, {
      plan: "free",
      slug: newSlug,
    }),
  ]);
}

export function createPlugins(db: dbClient) {
  return [
    socialProvidersPlugin(),
    ...(process.env.NEXT_PUBLIC_KAN_ENV === "cloud"
      ? [
          stripe({
            stripeClient: createStripeClient(),
            stripeWebhookSecret: process.env.STRIPE_WEBHOOK_SECRET!,
            createCustomerOnSignUp: true,
            subscription: {
              enabled: true,
              plans: [
                {
                  name: "team",
                  priceId: process.env.STRIPE_TEAM_PLAN_MONTHLY_PRICE_ID!,
                  annualDiscountPriceId:
                    process.env.STRIPE_TEAM_PLAN_YEARLY_PRICE_ID!,
                  freeTrial: {
                    days: 14,
                    onTrialStart: async (subscription) => {
                      await triggerWorkflow(db, "trial-start", subscription);
                    },
                    onTrialEnd: async ({ subscription }) => {
                      await triggerWorkflow(db, "trial-end", subscription);
                    },
                    onTrialExpired: async (subscription) => {
                      await triggerWorkflow(db, "trial-expired", subscription);
                    },
                  },
                },
                {
                  name: "pro",
                  priceId: process.env.STRIPE_PRO_PLAN_MONTHLY_PRICE_ID!,
                  annualDiscountPriceId:
                    process.env.STRIPE_PRO_PLAN_YEARLY_PRICE_ID!,
                  freeTrial: {
                    days: 14,
                    onTrialStart: async (subscription) => {
                      await triggerWorkflow(db, "trial-start", subscription);
                    },
                    onTrialEnd: async ({ subscription }) => {
                      await triggerWorkflow(db, "trial-end", subscription);
                    },
                    onTrialExpired: async (subscription) => {
                      await triggerWorkflow(db, "trial-expired", subscription);
                    },
                  },
                },
              ],
              authorizeReference: async (data) => {
                const workspace = await workspaceRepo.getByPublicId(
                  db,
                  data.referenceId,
                );

                if (!workspace) {
                  return Promise.resolve(false);
                }

                const isUserInWorkspace = await workspaceRepo.isUserInWorkspace(
                  db,
                  data.user.id,
                  workspace.id,
                );

                return isUserInWorkspace;
              },
              getCheckoutSessionParams: () => {
                return {
                  params: {
                    allow_promotion_codes: true,
                  },
                };
              },
              onSubscriptionComplete: async ({
                subscription,
                stripeSubscription,
              }) => {
                // Set unlimited seats to true for pro plans
                if (subscription.plan === "pro") {
                  await subscriptionRepo.updateByStripeSubscriptionId(
                    db,
                    stripeSubscription.id,
                    {
                      unlimitedSeats: true,
                    },
                  );
                  log.info(
                    { subscriptionId: stripeSubscription.id },
                    "Pro subscription activated with unlimited seats",
                  );

                  const workspace = await workspaceRepo.getByPublicId(
                    db,
                    subscription.referenceId,
                  );

                  if (workspace?.id) {
                    await memberRepo.unpauseAllMembers(db, workspace.id);
                  }
                }
              },
              onSubscriptionCancel: async ({
                subscription,
                cancellationDetails,
              }) => {
                await triggerWorkflow(
                  db,
                  "subscription-canceled",
                  subscription,
                  cancellationDetails,
                );
              },
              onSubscriptionDeleted: async ({ subscription }) => {
                await cancelWorkspaceAccess(db, subscription.referenceId);
              },
              onSubscriptionUpdate: async ({ subscription }) => {
                await triggerWorkflow(db, "subscription-updated", subscription);
              },
            },
          }),
        ]
      : []),
    apiKey({
      enableSessionForAPIKeys: true,
      customAPIKeyGetter: (ctx) => {
        const authorization = ctx.headers?.get("authorization");
        if (authorization?.startsWith("Bearer ")) {
          return authorization.slice(7);
        }
        return ctx.headers?.get("x-api-key") ?? null;
      },
      rateLimit: {
        enabled: true,
        timeWindow: 1000 * 60, // 1 minute
        maxRequests: 100, // 100 requests per minute
      },
    }),
    magicLink({
      expiresIn: 60 * 60 * 24 * 7, // 7 days
      sendMagicLink: async ({ email, url }) => {
        try {
          const decodedUrl = decodeURIComponent(url);
          log.info(
            { email, isInvite: decodedUrl.includes("type=invite") },
            "Sending magic link",
          );
          if (decodedUrl.includes("type=invite")) {
            let inviterName = "";
            let workspaceName = "";

            try {
              const urlObj = new URL(url);
              const callbackUrl = urlObj.searchParams.get("callbackURL");
              if (callbackUrl) {
                const callbackParams = new URL(
                  callbackUrl,
                  process.env.NEXT_PUBLIC_BASE_URL,
                ).searchParams;
                const memberPublicId = callbackParams.get("memberPublicId");

                if (memberPublicId) {
                  const member = await memberRepo.getByPublicId(
                    db,
                    memberPublicId,
                  );
                  if (member) {
                    const [workspace, inviter] = await Promise.all([
                      workspaceRepo.getById(db, member.workspaceId),
                      userRepo.getById(db, member.createdBy),
                    ]);

                    if (workspace) workspaceName = workspace.name;
                    if (inviter) inviterName = inviter.name ?? "";
                  }
                }
              }
            } catch (error) {
              log.error({ err: error }, "Failed to fetch invite details");
            }

            await sendEmail(
              email,
              workspaceName
                ? `Invitation to join the workspace ${workspaceName}`
                : "Invitation to join workspace",
              "JOIN_WORKSPACE",
              {
                magicLoginUrl: url,
                inviterName,
                workspaceName,
              },
            );
          } else {
            await sendEmail(
              email,
              process.env.NEXT_PUBLIC_WHITE_LABEL_HIDE_POWERED_BY === "true"
                ? "Sign in to your account"
                : "Sign in to Kan",
              "MAGIC_LINK",
              {
                magicLoginUrl: url,
              },
            );
          }
        } catch (error) {
          log.error({ err: error, email }, "Error sending magic link");
        }
      },
    }),
    // Generic OIDC provider
    ...(process.env.OIDC_CLIENT_ID &&
    process.env.OIDC_CLIENT_SECRET &&
    process.env.OIDC_DISCOVERY_URL
      ? [
          genericOAuth({
            config: [
              {
                providerId: "oidc",
                clientId: process.env.OIDC_CLIENT_ID,
                clientSecret: process.env.OIDC_CLIENT_SECRET,
                discoveryUrl: process.env.OIDC_DISCOVERY_URL,
                scopes: ["openid", "email", "profile"],
                pkce: true,
                mapProfileToUser: (profile: {
                  name?: string;
                  display_name?: string;
                  preferred_username?: string;
                  given_name?: string;
                  family_name?: string;
                  email?: string;
                  email_verified?: boolean;
                  sub?: string;
                  picture?: string;
                  avatar?: string;
                }) => {
                  log.debug({ profile }, "OIDC profile received");

                  const name =
                    profile.name ??
                    profile.display_name ??
                    profile.preferred_username ??
                    (profile.given_name && profile.family_name
                      ? `${profile.given_name} ${profile.family_name}`.trim()
                      : (profile.given_name ?? profile.family_name)) ??
                    profile.sub ??
                    "";

                  return {
                    email: profile.email,
                    name: name,
                    emailVerified: profile.email_verified ?? false,
                    image: profile.picture ?? profile.avatar ?? null,
                  };
                },
              },
            ],
          }),
        ]
      : []),
  ];
}
