import { NextResponse } from "next/server";
import { getAuthFromRequest } from "@open-mercato/shared/lib/auth/server";
import { toAbsoluteUrl } from "@open-mercato/shared/lib/url";
import { createRequestContainer } from "@open-mercato/shared/lib/di/container";
import { createConnectedChannelRow, MailboxAlreadyConnectedError } from "../../../../../lib/connect-channel.js";
import { getChannelAdapter } from "../../../../../lib/adapter-registry-singleton.js";
import { resolveOAuthClientCredentials } from "../../../../../lib/oauth-client-config.js";
import {
  COMMUNICATION_CHANNELS_OAUTH_STATE_COOKIE_NAME,
  DEFAULT_OAUTH_RETURN_URL,
  normalizeOAuthReturnUrl,
  OAuthStateError,
  verifyOAuthState
} from "../../../../../lib/oauth-state.js";
import { createLogger } from "@open-mercato/shared/lib/logger";
const logger = createLogger("communication_channels").child({ component: "oauth-callback" });
const metadata = {
  path: "/communication_channels/oauth/[provider]/callback",
  // No auth feature gate — the state cookie carries identity. The route still
  // verifies the session below to bind the callback to its initiator.
  GET: { requireAuth: true }
};
function redirectWithFlash(req, returnUrl, flash) {
  const base = new URL(
    normalizeOAuthReturnUrl(returnUrl, DEFAULT_OAUTH_RETURN_URL),
    new URL(req.url).origin
  );
  base.searchParams.set("flash", flash.type);
  if (flash.code) base.searchParams.set("code", flash.code);
  if (flash.provider) base.searchParams.set("provider", flash.provider);
  if (flash.channelId) base.searchParams.set("channelId", flash.channelId);
  const response = NextResponse.redirect(base.toString(), 302);
  response.cookies.set({
    name: COMMUNICATION_CHANNELS_OAUTH_STATE_COOKIE_NAME,
    value: "",
    httpOnly: true,
    secure: process.env.NODE_ENV === "production",
    sameSite: "lax",
    path: "/",
    maxAge: 0
  });
  return response;
}
async function GET(req, context) {
  const { provider } = await context.params;
  const url = new URL(req.url);
  const code = url.searchParams.get("code") ?? "";
  const stateParam = url.searchParams.get("state") ?? "";
  const error = url.searchParams.get("error");
  if (error) {
    return redirectWithFlash(req, DEFAULT_OAUTH_RETURN_URL, { type: "error", code: error, provider });
  }
  if (!code || !stateParam) {
    return redirectWithFlash(req, DEFAULT_OAUTH_RETURN_URL, {
      type: "error",
      code: "missing_code_or_state",
      provider
    });
  }
  const auth = await getAuthFromRequest(req);
  if (!auth?.sub || !auth?.tenantId) {
    return redirectWithFlash(req, DEFAULT_OAUTH_RETURN_URL, {
      type: "error",
      code: "unauthorized",
      provider
    });
  }
  const adapter = getChannelAdapter(provider);
  if (!adapter || typeof adapter.exchangeOAuthCode !== "function") {
    return redirectWithFlash(req, DEFAULT_OAUTH_RETURN_URL, {
      type: "error",
      code: "unknown_provider",
      provider
    });
  }
  const cookieValue = req.headers.get("cookie") ?? "";
  const stateCookie = parseCookie(cookieValue, COMMUNICATION_CHANNELS_OAUTH_STATE_COOKIE_NAME);
  let statePayload;
  try {
    statePayload = verifyOAuthState({
      cookie: stateCookie,
      expectedUserId: auth.sub,
      expectedProviderKey: provider,
      expectedState: stateParam
    });
  } catch (err) {
    const errCode = err instanceof OAuthStateError ? err.code : "invalid_state";
    return redirectWithFlash(req, DEFAULT_OAUTH_RETURN_URL, {
      type: "error",
      code: errCode,
      provider
    });
  }
  if (statePayload.tenantId !== auth.tenantId) {
    return redirectWithFlash(req, DEFAULT_OAUTH_RETURN_URL, {
      type: "error",
      code: "tenant_mismatch",
      provider
    });
  }
  const returnUrl = normalizeOAuthReturnUrl(statePayload.returnUrl, DEFAULT_OAUTH_RETURN_URL);
  const container = await createRequestContainer();
  const credentialsService = (() => {
    try {
      return container.resolve("integrationCredentialsService");
    } catch {
      return null;
    }
  })();
  const oauthClientCredentials = await resolveOAuthClientCredentials(credentialsService, provider, {
    tenantId: statePayload.tenantId,
    organizationId: statePayload.organizationId ?? null
  });
  if (!oauthClientCredentials) {
    return redirectWithFlash(req, returnUrl, {
      type: "error",
      code: "oauth_client_not_configured",
      provider
    });
  }
  const redirectUri = toAbsoluteUrl(req, `/api/communication_channels/oauth/${provider}/callback`);
  let exchange;
  try {
    exchange = await adapter.exchangeOAuthCode({
      code,
      redirectUri,
      credentials: oauthClientCredentials,
      scope: {
        tenantId: statePayload.tenantId,
        organizationId: statePayload.organizationId ?? statePayload.tenantId
      },
      stateExtra: statePayload.extra
    });
  } catch (err) {
    logger.warn(
      "code exchange failed for provider",
      { provider, err }
    );
    return redirectWithFlash(req, returnUrl, {
      type: "error",
      code: "exchange_failed",
      provider
    });
  }
  const em = container.resolve("em").fork();
  const credentialsScope = {
    tenantId: statePayload.tenantId,
    organizationId: statePayload.organizationId ?? statePayload.tenantId,
    userId: auth.sub
  };
  let credentialsRefId = null;
  let credentialsPersisted = false;
  if (credentialsService?.save) {
    try {
      await credentialsService.save(
        `channel_${provider}`,
        {
          ...exchange.credentials,
          userId: auth.sub,
          expiresAt: exchange.expiresAt ? exchange.expiresAt.toISOString() : void 0
        },
        credentialsScope
      );
      credentialsPersisted = true;
    } catch (err) {
      logger.warn(
        "persisting credentials failed for provider",
        { provider, err }
      );
    }
  }
  if (credentialsPersisted) {
    try {
      const { IntegrationCredentials } = await import("@open-mercato/core/modules/integrations/data/entities");
      const { findOneWithDecryption } = await import("@open-mercato/shared/lib/encryption/find");
      const row = await findOneWithDecryption(
        em,
        IntegrationCredentials,
        {
          integrationId: `channel_${provider}`,
          tenantId: credentialsScope.tenantId,
          organizationId: credentialsScope.organizationId,
          userId: credentialsScope.userId,
          deletedAt: null
        },
        void 0,
        credentialsScope
      );
      credentialsRefId = row?.id ?? null;
    } catch {
      credentialsRefId = null;
    }
  }
  const displayName = exchange.displayName ?? exchange.externalIdentifier ?? `${provider} channel`;
  const credentialsAvailable = credentialsRefId !== null;
  let channel;
  try {
    channel = await createConnectedChannelRow({
      em,
      adapter,
      providerKey: provider,
      displayName,
      externalIdentifier: exchange.externalIdentifier ?? null,
      credentialsRefId,
      userId: auth.sub,
      scope: { tenantId: statePayload.tenantId, organizationId: statePayload.organizationId ?? null }
    });
  } catch (err) {
    if (err instanceof MailboxAlreadyConnectedError) {
      logger.warn("mailbox already connected via another provider", { existingProviderKey: err.existingProviderKey });
      return redirectWithFlash(req, returnUrl, {
        type: "error",
        code: "mailbox_already_connected",
        provider
      });
    }
    throw err;
  }
  const adapterSupportsPush = typeof adapter.registerPush === "function" && typeof adapter.unregisterPush === "function";
  if (credentialsAvailable && adapterSupportsPush && statePayload.organizationId && provider === "gmail") {
    try {
      const { pushRegister } = await import("../../../../../commands/push-register.js");
      await pushRegister({
        container,
        scope: {
          tenantId: statePayload.tenantId,
          organizationId: statePayload.organizationId,
          userId: auth.sub
        },
        input: { channelId: channel.id }
      });
    } catch (err) {
      logger.warn(
        "best-effort pushRegister failed for channel",
        { channelId: channel.id, err }
      );
    }
  }
  return redirectWithFlash(req, returnUrl, {
    type: "connected",
    provider,
    channelId: channel.id
  });
}
function parseCookie(header, name) {
  if (!header) return null;
  const segments = header.split(";");
  for (const segment of segments) {
    const trimmed = segment.trim();
    if (trimmed.startsWith(`${name}=`)) {
      return decodeURIComponent(trimmed.slice(name.length + 1));
    }
  }
  return null;
}
const openApi = {
  tags: ["CommunicationChannels"],
  methods: {
    GET: {
      summary: "OAuth callback \u2014 exchange code, persist credentials, create per-user channel",
      tags: ["CommunicationChannels"],
      responses: [
        { status: 302, description: "Redirect back to returnUrl with flash query params" }
      ]
    }
  }
};
var route_default = GET;
export {
  GET,
  route_default as default,
  metadata,
  openApi
};
//# sourceMappingURL=route.js.map
