import { betterAuth } from "better-auth";
import { drizzleAdapter } from "better-auth/adapters/drizzle";
import { APIError } from "better-auth/api";
import { nextCookies } from "better-auth/next-js";
import { admin, organization } from "better-auth/plugins";
import { emailOTP } from "better-auth/plugins/email-otp";
import { nanoid } from "nanoid";

import { db } from "@/lib/db";
import * as schema from "@/lib/db/schema";
import { proEmailRejectMessage, validateProEmail } from "@/lib/email-validation";
import { env } from "@/lib/env";
import { createPersonalOrg } from "@/lib/onboarding";

export const auth = betterAuth({
  appName: "Eleva Speed",
  database: drizzleAdapter(db, {
    provider: "pg",
    schema,
  }),
  emailAndPassword: {
    enabled: true,
  },
  databaseHooks: {
    user: {
      create: {
        before: async (data) => {
          const validation = await validateProEmail(data.email);
          if (!validation.valid) {
            throw new APIError("BAD_REQUEST", {
              message: proEmailRejectMessage(validation.reason),
            });
          }
          return { data };
        },
        after: async (data) => {
          // Personal Org — every signup gets one (Phase 1).
          const { orgId } = await createPersonalOrg({
            id: data.id,
            email: data.email,
            name: data.name,
          });

          // Subscription stub — every org starts on Free (Phase 5).
          await db
            .insert(schema.orgSubscription)
            .values({
              id: `sub_${orgId}`,
              organizationId: orgId,
              plan: "free",
              status: "active",
            })
            .onConflictDoNothing({ target: schema.orgSubscription.organizationId });

          // Marketing lead capture (existing behavior).
          await db.insert(schema.leads).values({
            id: nanoid(),
            email: data.email,
            source: "signup",
            consent: false,
            userId: data.id,
          });
        },
      },
    },
  },
  plugins: [
    admin(),
    organization({
      sendInvitationEmail: async (data) => {
        const { sendInvitationEmail } = await import("@/lib/email/invitations");
        const baseUrl =
          env.BETTER_AUTH_URL ?? (env.NODE_ENV !== "production" ? "http://localhost:3000" : null);
        if (!baseUrl) {
          throw new Error(
            "BETTER_AUTH_URL is not configured — refusing to send invitation email with a localhost link. Set BETTER_AUTH_URL to the public app URL (e.g. https://app.elevaseo.com).",
          );
        }
        const url = `${baseUrl}/accept-invitation/${data.id}`;
        await sendInvitationEmail({
          to: data.email,
          inviterName: data.inviter.user.name ?? data.inviter.user.email,
          organizationName: data.organization.name,
          role: data.role,
          acceptUrl: url,
        });
      },
    }),
    emailOTP({
      sendVerificationOnSignUp: true,
      otpLength: 6,
      expiresIn: 300,
      sendVerificationOTP: async ({ email, otp, type }) => {
        const { sendOTPEmail } = await import("@/lib/email/otp");
        await sendOTPEmail({ to: email, otp, type });
      },
    }),
    nextCookies(),
  ],
  // Better Auth origin allowlist. Composed of:
  //   - BETTER_AUTH_URL (the primary public host this app is served from)
  //   - BETTER_AUTH_TRUSTED_ORIGINS (comma-separated, for aliases /
  //     additional public domains — e.g. when speed.elevaseo.com AND
  //     speed.elevaseo.app both point at the same instance, or when the
  //     embed widget runs on the brand marketing domain)
  //   - localhost / 127.0.0.1 for dev
  // Empty strings and duplicates are dropped.
  trustedOrigins: [
    process.env.BETTER_AUTH_URL || "http://localhost:3000",
    ...(process.env.BETTER_AUTH_TRUSTED_ORIGINS ?? "")
      .split(",")
      .map((s) => s.trim())
      .filter(Boolean),
    "http://localhost:3000",
    "http://127.0.0.1:3000",
  ].filter((v, i, a) => v.length > 0 && a.indexOf(v) === i),
});

export type Session = typeof auth.$Infer.Session;
