/**
 * WhatsApp Business OAuth Callback
 *
 * POST /api/integrations/whatsapp/oauth/callback
 *
 * Receives the authorization code from Meta Embedded Signup,
 * exchanges it for a long-lived access token, stores credentials,
 * and auto-registers the webhook.
 */

import { NextRequest, NextResponse } from "next/server";
import { cookies } from "next/headers";
import { auth } from "@/lib/auth";
import { logger } from "@/lib/logger";
import { encrypt } from "@/lib/encryption";
import { convexMutation } from "@/lib/convex-client";
import { prisma } from "@/lib/db";
import { z } from "zod";
import { applyRateLimit, rateLimiters } from "@/lib/rate-limiter";
import { registerWebhook } from "@/lib/services/whatsapp-webhook-registration";

// ── Constants ───────────────────────────────────────────────────────────

const META_GRAPH_API = "https://graph.facebook.com/v19.0";
const STATE_COOKIE_NAME = "wa-oauth-state";

// ── Request schema ──────────────────────────────────────────────────────

const oauthCallbackSchema = z.object({
  code: z.string().min(1, "Authorization code is required"),
  state: z.string().min(1, "State token is required"),
});

// ── Types ───────────────────────────────────────────────────────────────

interface OAuthCallbackResponse {
  success: boolean;
  message?: string;
  error?: string;
  data?: {
    phoneNumber: string;
    displayName: string;
    wabaId: string;
  };
}

interface MetaTokenResponse {
  access_token: string;
  token_type: string;
  expires_in?: number;
}

interface MetaDebugTokenResponse {
  data: {
    app_id: string;
    type: string;
    is_valid: boolean;
    scopes: string[];
    granular_scopes?: Array<{
      scope: string;
      target_ids?: string[];
    }>;
  };
}

interface MetaPhoneNumberData {
  id: string;
  display_phone_number: string;
  verified_name: string;
  quality_rating: string;
}

// ── Helpers ─────────────────────────────────────────────────────────────

function getMetaAppId(): string {
  const id = process.env.META_APP_ID || process.env.NEXT_PUBLIC_META_APP_ID;
  if (!id) throw new Error("META_APP_ID is not configured");
  return id;
}

function getMetaAppSecret(): string {
  const secret = process.env.META_APP_SECRET;
  if (!secret) throw new Error("META_APP_SECRET is not configured");
  return secret;
}

/**
 * Exchange the authorization code for a short-lived access token.
 */
async function exchangeCodeForToken(code: string): Promise<string> {
  const appId = getMetaAppId();
  const appSecret = getMetaAppSecret();

  const url = new URL(`${META_GRAPH_API}/oauth/access_token`);
  url.searchParams.set("client_id", appId);
  url.searchParams.set("client_secret", appSecret);
  url.searchParams.set("code", code);

  const response = await fetch(url.toString());
  if (!response.ok) {
    const error = await response.json().catch(() => ({}));
    logger.error("Meta code exchange failed", { status: response.status, error });
    throw new Error(
      error?.error?.message || `Code exchange failed with status ${response.status}`,
    );
  }

  const data: MetaTokenResponse = await response.json();
  return data.access_token;
}

/**
 * Exchange a short-lived token for a long-lived token (~60 days).
 */
async function exchangeForLongLivedToken(
  shortLivedToken: string,
): Promise<string> {
  const appId = getMetaAppId();
  const appSecret = getMetaAppSecret();

  const url = new URL(`${META_GRAPH_API}/oauth/access_token`);
  url.searchParams.set("grant_type", "fb_exchange_token");
  url.searchParams.set("client_id", appId);
  url.searchParams.set("client_secret", appSecret);
  url.searchParams.set("fb_exchange_token", shortLivedToken);

  const response = await fetch(url.toString());
  if (!response.ok) {
    const error = await response.json().catch(() => ({}));
    logger.error("Long-lived token exchange failed", {
      status: response.status,
      error,
    });
    throw new Error(
      error?.error?.message ||
        `Long-lived token exchange failed with status ${response.status}`,
    );
  }

  const data: MetaTokenResponse = await response.json();
  return data.access_token;
}

/**
 * Use debug_token to inspect the token and find the shared WABA ID.
 */
async function getSharedWabaId(accessToken: string): Promise<string> {
  const url = new URL(`${META_GRAPH_API}/debug_token`);
  url.searchParams.set("input_token", accessToken);
  url.searchParams.set("access_token", accessToken);

  const response = await fetch(url.toString());
  if (!response.ok) {
    const error = await response.json().catch(() => ({}));
    throw new Error(
      error?.error?.message || "Failed to debug token",
    );
  }

  const data: MetaDebugTokenResponse = await response.json();

  // The WABA ID is in granular_scopes for whatsapp_business_management
  const wabaScope = data.data.granular_scopes?.find(
    (s) =>
      s.scope === "whatsapp_business_management" ||
      s.scope === "whatsapp_business_messaging",
  );

  if (!wabaScope?.target_ids?.[0]) {
    throw new Error(
      "No WhatsApp Business Account found in token permissions. " +
        "Please ensure you shared a WhatsApp Business Account during signup.",
    );
  }

  return wabaScope.target_ids[0];
}

/**
 * Get the phone numbers associated with a WABA.
 */
async function getWabaPhoneNumbers(
  wabaId: string,
  accessToken: string,
): Promise<MetaPhoneNumberData> {
  const url = new URL(`${META_GRAPH_API}/${wabaId}/phone_numbers`);
  url.searchParams.set("access_token", accessToken);

  const response = await fetch(url.toString());
  if (!response.ok) {
    const error = await response.json().catch(() => ({}));
    throw new Error(
      error?.error?.message || "Failed to fetch phone numbers from WABA",
    );
  }

  const data = await response.json();

  if (!data.data || data.data.length === 0) {
    throw new Error(
      "No phone numbers found on the WhatsApp Business Account. " +
        "Please add a phone number in Meta Business Suite first.",
    );
  }

  // Use the first phone number
  return data.data[0] as MetaPhoneNumberData;
}

// ── Route Handler ───────────────────────────────────────────────────────

export async function POST(request: NextRequest) {
  try {
    // Rate limit
    const rateLimitResponse = await applyRateLimit(
      request,
      rateLimiters.authGeneral,
    );
    if (rateLimitResponse) return rateLimitResponse;

    // Authenticate user
    const session = await auth();
    if (!session?.user?.id) {
      return NextResponse.json<OAuthCallbackResponse>(
        { success: false, error: "Unauthorized. Please log in." },
        { status: 401 },
      );
    }

    const userId = session.user.id;

    // Parse and validate request body
    const body = await request.json();
    const validation = oauthCallbackSchema.safeParse(body);

    if (!validation.success) {
      return NextResponse.json<OAuthCallbackResponse>(
        { success: false, error: validation.error.errors[0].message },
        { status: 400 },
      );
    }

    const { code, state } = validation.data;

    // CSRF protection: verify the state token matches the one we stored in
    // the HttpOnly cookie when the frontend requested /oauth/state. This
    // ensures the authorization code was obtained from a flow initiated by
    // our own UI and not forged by a third party.
    const cookieStore = cookies();
    const cookieState = cookieStore.get(STATE_COOKIE_NAME)?.value;

    if (!cookieState || cookieState !== state) {
      logger.security("WhatsApp OAuth state mismatch", {
        hasCookie: !!cookieState,
        userId,
      });
      return NextResponse.json<OAuthCallbackResponse>(
        { success: false, error: "Invalid state token" },
        { status: 403 },
      );
    }

    logger.info("WhatsApp OAuth callback received", { userId });

    // 1. Find the user's organization
    const org = await prisma.organizations.findFirst({
      where: { ownerId: userId },
    });

    if (!org) {
      return NextResponse.json<OAuthCallbackResponse>(
        {
          success: false,
          error:
            "No organization found. Please create an organization first.",
        },
        { status: 400 },
      );
    }

    // 2. Exchange authorization code for short-lived token
    logger.info("Exchanging authorization code for token", { userId });
    const shortLivedToken = await exchangeCodeForToken(code);

    // 3. Exchange for long-lived token
    logger.info("Exchanging for long-lived token", { userId });
    const longLivedToken = await exchangeForLongLivedToken(shortLivedToken);

    // 4. Get the shared WABA ID from token
    logger.info("Inspecting token for WABA ID", { userId });
    const wabaId = await getSharedWabaId(longLivedToken);

    // 5. Get phone numbers from the WABA
    logger.info("Fetching WABA phone numbers", { userId, wabaId });
    const phoneNumber = await getWabaPhoneNumbers(wabaId, longLivedToken);

    // 6. Store credentials via Convex mutation
    logger.info("Storing WhatsApp credentials", {
      userId,
      phoneNumberId: phoneNumber.id,
      displayNumber: phoneNumber.display_phone_number,
    });

    const appSecret = getMetaAppSecret();
    const orgId = org.id;

    await convexMutation("organizations/mutations:updateOrganizationNoAuth", {
      organizationId: orgId,
      whatsappPhoneNumberId: phoneNumber.id,
      whatsappAccessTokenEncrypted: encrypt(longLivedToken),
      whatsappAppSecretEncrypted: encrypt(appSecret),
      whatsappDisplayNumber: phoneNumber.display_phone_number,
      whatsappWebhookVerified: false,
      whatsappConfiguredAt: Date.now(),
      whatsappConfiguredBy: userId,
      // Track issuance time for the long-lived token so the refresh service
      // can rotate it before the ~60-day Meta expiry.
      whatsappTokenIssuedAt: Date.now(),
    });

    // 7. Auto-register webhook with Meta
    logger.info("Auto-registering webhook", { userId, wabaId });
    const webhookResult = await registerWebhook(wabaId, longLivedToken);

    if (!webhookResult.success) {
      logger.warn("Webhook auto-registration failed (non-blocking)", {
        userId,
        wabaId,
        error: webhookResult.error,
      });
      // Non-blocking: credentials are saved, webhook can be registered later
    } else {
      // Update webhook verification status
      await convexMutation("organizations/mutations:updateOrganizationNoAuth", {
        organizationId: orgId,
        whatsappWebhookVerified: true,
      });
      logger.info("Webhook auto-registered successfully", { userId, wabaId });
    }

    logger.info("WhatsApp OAuth connection completed", {
      userId,
      wabaId,
      phoneNumberId: phoneNumber.id,
      displayNumber: phoneNumber.display_phone_number,
      webhookRegistered: webhookResult.success,
    });

    const successResponse = NextResponse.json<OAuthCallbackResponse>(
      {
        success: true,
        message: webhookResult.success
          ? "WhatsApp Business account connected and webhook registered successfully"
          : "WhatsApp Business account connected. Webhook registration pending.",
        data: {
          phoneNumber: phoneNumber.display_phone_number,
          displayName: phoneNumber.verified_name,
          wabaId,
        },
      },
      { status: 200 },
    );

    // One-time use: delete the state cookie so it cannot be replayed.
    successResponse.cookies.delete(STATE_COOKIE_NAME);

    return successResponse;
  } catch (error) {
    const message =
      error instanceof Error ? error.message : "Unknown error occurred";

    logger.error("WhatsApp OAuth callback error", {
      error: message,
      stack: error instanceof Error ? error.stack : undefined,
    });

    return NextResponse.json<OAuthCallbackResponse>(
      {
        success: false,
        error:
          message.includes("META_APP")
            ? "Server configuration error. Please contact support."
            : message,
      },
      { status: 500 },
    );
  }
}

// Only allow POST method
export async function GET() {
  return NextResponse.json({ error: "Method not allowed" }, { status: 405 });
}
