import { NextResponse } from 'next/server';
import { auth } from './app/api/auth/[...nextauth]/edge-auth';
import { getSessionCookieName } from './lib/auth/sessionCookies';
import { i18nMiddleware, shouldSkipI18n } from './middleware/i18n';

// Minimal, Edge-safe middleware: API key header presence check for select API routes
// and auth gate for /msp paths, plus i18n locale resolution. Heavy logic stays in route handlers.
//
// Important: for `/api/*` routes, this middleware runs before the route handler. A route can be
// session-authenticated at the handler level and still fail here with `Unauthorized: API key missing`
// unless it is explicitly allowlisted below.

// =============================================================================
// CORS Configuration - Allow all origins
// =============================================================================

/**
 * Apply CORS headers to a response, allowing all origins.
 */
function applyCorsHeaders(response: NextResponse, origin: string | null): NextResponse {
  response.headers.set('Access-Control-Allow-Origin', origin || '*');
  response.headers.set('Access-Control-Allow-Credentials', 'true');
  if (origin) {
    response.headers.set('Vary', 'Origin');
  }
  return response;
}

/**
 * Create a CORS preflight response for OPTIONS requests.
 */
function corsPreflightResponse(origin: string | null): NextResponse {
  const response = new NextResponse(null, { status: 204 });
  response.headers.set('Access-Control-Allow-Origin', origin || '*');
  response.headers.set('Access-Control-Allow-Methods', 'GET,POST,PUT,PATCH,DELETE,OPTIONS');
  response.headers.set('Access-Control-Allow-Headers', 'Content-Type,Authorization,X-API-Key,X-Tenant-ID,X-Request-ID,X-Idempotency-Key');
  response.headers.set('Access-Control-Allow-Credentials', 'true');
  response.headers.set('Access-Control-Max-Age', '86400');
  if (origin) {
    response.headers.set('Vary', 'Origin, Access-Control-Request-Headers');
  }
  return response;
}

// =============================================================================
// Middleware
// =============================================================================
const protectedPrefix = '/msp';
const clientPortalPrefix = '/client-portal';

// Helper function to get canonical URL (reads env var dynamically for testing)
function getCanonicalUrl(): URL | null {
  return process.env.NEXTAUTH_URL ? new URL(process.env.NEXTAUTH_URL) : null;
}

const apiKeySkipPaths = [
  '/api/health',
  '/api/healthz',
  '/api/readyz',
  '/api/documents/download/',
  '/api/documents/view/',
  '/api/email/webhooks/',
  '/api/calendar/webhooks/',
  '/api/calendar/appointment/',
  '/api/email/oauth/',
  '/api/teams/auth/',
  '/api/teams/bot/',
  '/api/teams/message-extension/',
  '/api/teams/webhooks/',  // Microsoft Graph change notifications; authenticated via clientState secret in the route
  '/api/teams/package/download',
  '/api/client-portal/domain-session',
  // Mobile auth endpoints use OTT/refresh tokens (no x-api-key)
  '/api/v1/mobile/auth/',
  // Mobile IAP endpoints are pre-account: provisioning happens before the
  // user has any tenant/api-key; restore + check-email + the Apple webhook
  // are also unauthenticated by design.
  '/api/v1/mobile/iap/',
  // Mobile account endpoints use Bearer auth (validated in route handler)
  '/api/v1/mobile/account/',
  '/api/integrations/ninjaone/callback',
  '/api/integrations/xero/connect',
  '/api/integrations/xero/callback',
  '/api/integrations/qbo/connect',
  '/api/integrations/qbo/callback',
  // Entra integration API routes use session auth via requireEntraUiFlagEnabled
  '/api/integrations/entra/',
  // AI chat endpoints are session-authenticated (MSP UI)
  '/api/chat/',
  // Remote MCP server authenticates in-route (Alga API key OR IdP-delegated Bearer token)
  '/api/mcp',
  // MCP admin/provisioning APIs authenticate in-route (session admin OR API key)
  '/api/v1/mcp/',
  // Workflow definition/run APIs are session-authenticated for MSP workflow UI tooling.
  '/api/workflow-definitions',
  '/api/workflow-definitions/',
  '/api/workflow-runs',
  '/api/workflow-runs/',
  // Internal MSP UI endpoints (session-authenticated)
  '/api/accounting/csv/',
  '/api/accounting/exports/',
  '/api/webhooks/stripe',
  '/api/webhooks/ninjaone',
  '/api/webhooks/tacticalrmm',
  // Server-to-server webhooks from nm-store. Authenticated via HMAC
  // x-webhook-signature using ALGA_WEBHOOK_SECRET (verified in route handlers).
  '/api/billing/check-tenant',
  '/api/billing/licence-count',
  '/api/billing/licence-usage/',
  // Reactivation / win-back HMAC endpoints (server-to-server from nm-store and
  // the temporal worker; each verifies ALGA_WEBHOOK_SECRET in its route handler).
  '/api/billing/request-reactivation',
  '/api/billing/complete-reactivation',
  '/api/billing/reactivation-token',          // startsWith also covers /reactivation-token/session
  '/api/billing/reactivation-password-reset',
  '/api/inbound/',  // User-configurable inbound webhook receiver; auth per webhook config (HMAC / bearer / IP / path token)
  '/api/files/',   // File download routes use session auth
  '/api/share/',  // Public share link routes handle their own auth
  '/api/ext/',  // Extension API routes handle their own auth
  '/api/ext-proxy/',
  '/api/ext-debug/',  // Extension debug stream uses session auth
  '/api/internal/ext-storage/',  // Runner storage API uses x-runner-auth token
  '/api/internal/ext-runner/',   // Runner install-config/registry API uses x-runner-auth token
  '/api/internal/ext-scheduler/', // Runner scheduler host API uses x-runner-auth token
  '/api/internal/ext-invoicing/', // Runner invoicing host API uses x-runner-auth token
  '/api/internal/ext-clients/', // Runner client read host API uses x-runner-auth token
  '/api/internal/ext-services/', // Runner service read host API uses x-runner-auth token
];

export function shouldSkipApiKeyAuth(pathname: string): boolean {
  return apiKeySkipPaths.some((path) => pathname.startsWith(path)) ||
    (pathname.startsWith('/api/tickets/') && pathname.endsWith('/live-token')) ||
    (pathname.startsWith('/api/documents/') && (pathname.endsWith('/thumbnail') || pathname.endsWith('/preview')));
}

export function getVanityClientPortalInternalRedirectTarget(args: {
  pathname: string;
  isAuthPage: boolean;
  requestHostname: string;
  canonicalUrlEnv: URL | null;
  userType?: string | null;
}): URL | null {
  const {
    pathname,
    isAuthPage,
    requestHostname,
    canonicalUrlEnv,
    userType,
  } = args;

  if (
    userType !== 'internal' ||
    !canonicalUrlEnv ||
    requestHostname === canonicalUrlEnv.hostname
  ) {
    return null;
  }

  if (
    pathname === '/auth/client-portal/signin' ||
    (pathname.startsWith(clientPortalPrefix) && !isAuthPage)
  ) {
    return new URL('/msp/dashboard', canonicalUrlEnv.origin);
  }

  return null;
}

const _middleware = auth((request) => {
  const pathname = request.nextUrl.pathname;
  const requestHost = request.headers.get('host') || '';
  const requestHostname = requestHost.split(':')[0];
  const origin = request.headers.get('origin');
  const nextAction = request.headers.get('next-action');

  // Handle CORS preflight requests early
  if (request.method === 'OPTIONS') {
    return corsPreflightResponse(origin);
  }

  // Dev-only guard: prevent noisy stack traces from stale Server Action IDs after refactors/restarts.
  // If the browser is running an older bundle, it can keep POSTing an action id that no longer exists.
  if (process.env.NODE_ENV === 'development' && nextAction === '0091be379ebfc31238a6a78b24a504906087e8813c') {
    const errorResponse = NextResponse.json(
      { error: 'Stale Server Action request. Hard refresh the page (or close/reopen the tab) to load the latest bundle.' },
      { status: 409 }
    );
    return applyCorsHeaders(errorResponse, origin);
  }

  // Clone request headers so we can pass additional metadata downstream
  const requestHeaders = new Headers(request.headers);
  requestHeaders.set('x-pathname', pathname);

  // Create a response that will be modified throughout the middleware chain
  let response = NextResponse.next({
    request: {
      headers: requestHeaders,
    },
  });

  // Add pathname header for use in layouts (e.g., for branding injection)
  response.headers.set('x-pathname', pathname);

  // Apply i18n middleware first (unless path should skip it)
  if (!shouldSkipI18n(pathname)) {
    response = i18nMiddleware(request, response);
    // Ensure header persists after i18n adjustments
    response.headers.set('x-pathname', pathname);
  }

  // Only handle API routes that need API key authentication
  if (pathname.startsWith('/api/') && !pathname.startsWith('/api/auth/')) {
    const apiKey = request.headers.get('x-api-key');

    // Skip paths that don't need API authentication.
    // Any session-authenticated `/api/*` route must be added here or middleware will return
    // `401 Unauthorized: API key missing` before the route handler has a chance to run.
    // Log for debugging CORS issues
    if (process.env.NODE_ENV === 'development') {
      console.log('[CORS Middleware]', {
        pathname,
        origin,
        hasApiKey: !!apiKey,
        skipped: shouldSkipApiKeyAuth(pathname),
        method: request.method
      });
    }

    if (shouldSkipApiKeyAuth(pathname)) {
      return applyCorsHeaders(response, origin);
    }

    // For API routes that need authentication, check for API key presence only;
    // full validation happens in API route handlers (Node runtime)
    if (!apiKey) {
      const errorResponse = NextResponse.json(
        { error: 'Unauthorized: API key missing' },
        { status: 401 }
      );
      return applyCorsHeaders(errorResponse, origin);
    }
  }

  // Skip auth pages to prevent redirect loops
  const isAuthPage = pathname.startsWith('/auth/');

  // Redirect vanity domains to canonical for client portal signin (before auth check)
  if (pathname === '/auth/client-portal/signin') {
    const canonicalUrlEnv = getCanonicalUrl();

    if (canonicalUrlEnv && requestHostname !== canonicalUrlEnv.hostname) {
      const redirectTarget = getVanityClientPortalInternalRedirectTarget({
        pathname,
        isAuthPage,
        requestHostname,
        canonicalUrlEnv,
        userType: request.auth?.user?.user_type,
      });
      if (redirectTarget) {
        const redirectResponse = NextResponse.redirect(redirectTarget);
        redirectResponse.headers.set('x-pathname', redirectTarget.pathname);
        return redirectResponse;
      }

      const canonicalLogin = new URL('/auth/client-portal/signin', canonicalUrlEnv.origin);
      const hostHeader = request.headers.get('host') || requestHostname;

      // Preserve existing query params (like callbackUrl)
      request.nextUrl.searchParams.forEach((value, key) => {
        canonicalLogin.searchParams.set(key, value);
      });

      // Add portalDomain for branding
      canonicalLogin.searchParams.set('portalDomain', hostHeader);

      console.log('[middleware] signin vanity redirect', {
        requestHost: requestHostname,
        canonicalHost: canonicalUrlEnv.hostname,
        redirect: canonicalLogin.toString(),
      });

      const redirectResponse = NextResponse.redirect(canonicalLogin);
      redirectResponse.headers.set('x-pathname', canonicalLogin.pathname);
      return redirectResponse;
    }
  }

  // Test bypass: allow MSP routes without auth when explicitly enabled for E2E
  if (process.env.E2E_AUTH_BYPASS === 'true' && pathname.startsWith(protectedPrefix)) {
    // If a tenantId is provided via query param, stamp it into request headers
    const tenantId = request.nextUrl.searchParams.get('tenantId');
    if (tenantId) {
      response = NextResponse.next({
        request: {
          headers: new Headers({ ...Object.fromEntries(requestHeaders), 'x-tenant-id': tenantId }),
        },
      });
    }
    return applyCorsHeaders(response, origin);
  }

  // Protect MSP app routes: validate user type
  if (pathname.startsWith(protectedPrefix)) {
    if (!request.auth) {
      // In dev, Edge auth can occasionally fail to hydrate the session during HMR/middleware rebuilds.
      // If the browser still has a session cookie, avoid redirecting to /auth/signin (which looks like "being logged out").
      // Let the Node runtime handle auth in the page/server actions instead.
      if (process.env.NODE_ENV === 'development') {
        const sessionCookieName = getSessionCookieName();
        const hasSessionCookie =
          Boolean(request.cookies.get(sessionCookieName)?.value);
        if (hasSessionCookie) {
          return applyCorsHeaders(response, origin);
        }
      }

      // Next.js Server Actions are POST requests that expect an RSC payload. If we redirect here,
      // the client will follow the redirect and receive HTML, surfacing as:
      // "An unexpected response was received from the server."
      //
      // Instead, allow the request through and let the server action throw/handle auth (401).
      if (request.headers.has('next-action')) {
        return applyCorsHeaders(response, origin);
      }

      const loginUrl = request.nextUrl.clone();
      loginUrl.pathname = '/auth/signin';
      const callbackUrl = request.nextUrl.pathname + (request.nextUrl.search || '');
      loginUrl.searchParams.set('callbackUrl', callbackUrl);
      const redirectResponse = NextResponse.redirect(loginUrl);
      redirectResponse.headers.set('x-pathname', loginUrl.pathname);
      return redirectResponse;
    } else if (request.auth.user?.user_type !== 'internal') {
      // Redirect authenticated client users to their dashboard instead of trapping them in a login loop
      const canonicalUrlEnv = getCanonicalUrl();
      const redirectTarget = canonicalUrlEnv
        ? new URL('/client-portal/dashboard', canonicalUrlEnv.origin)
        : new URL('/client-portal/dashboard', request.nextUrl);
      const redirectResponse = NextResponse.redirect(redirectTarget);
      redirectResponse.headers.set('x-pathname', redirectTarget.pathname);
      return redirectResponse;
    }
  }

  // Protect Client Portal routes: validate user type (but not auth pages)
  if (pathname.startsWith(clientPortalPrefix) && !isAuthPage) {
    if (!request.auth) {
      // Same HMR-friendly behavior as /msp: avoid "logout-like" redirects when the session cookie exists.
      if (process.env.NODE_ENV === 'development') {
        const sessionCookieName = getSessionCookieName();
        const hasSessionCookie =
          Boolean(request.cookies.get(sessionCookieName)?.value);
        if (hasSessionCookie) {
          return applyCorsHeaders(response, origin);
        }
      }

      const callbackUrlAbsolute = new URL(request.nextUrl.pathname + (request.nextUrl.search || ''), request.nextUrl);
      const canonicalUrlEnv = getCanonicalUrl();

      if (canonicalUrlEnv && requestHostname !== canonicalUrlEnv.hostname) {
        const canonicalLogin = new URL('/auth/client-portal/signin', canonicalUrlEnv.origin);
        const hostHeader = request.headers.get('host') || requestHostname;
        const protocol = request.nextUrl.protocol.replace(/:$/, '');
        const callbackUrl = `${protocol}://${hostHeader}${request.nextUrl.pathname}${request.nextUrl.search}`;
        canonicalLogin.searchParams.set('callbackUrl', callbackUrl);
        canonicalLogin.searchParams.set('portalDomain', hostHeader);
        console.log('[middleware] vanity redirect', {
          requestHost: requestHostname,
          callback: callbackUrl,
          redirect: canonicalLogin.toString(),
        });
        const redirectResponse = NextResponse.redirect(canonicalLogin);
        redirectResponse.headers.set('x-pathname', canonicalLogin.pathname);
        return redirectResponse;
      }

      const loginUrl = request.nextUrl.clone();
      loginUrl.pathname = '/auth/client-portal/signin';
      const existingCallback = request.nextUrl.searchParams.get('callbackUrl');
      if (existingCallback) {
        loginUrl.searchParams.set('callbackUrl', existingCallback);
      } else {
        loginUrl.searchParams.set('callbackUrl', callbackUrlAbsolute.pathname + callbackUrlAbsolute.search);
      }
      const redirectResponse = NextResponse.redirect(loginUrl);
      redirectResponse.headers.set('x-pathname', loginUrl.pathname);
      return redirectResponse;
    } else if (request.auth.user?.user_type !== 'client') {
      const redirectTarget = getVanityClientPortalInternalRedirectTarget({
        pathname,
        isAuthPage,
        requestHostname,
        canonicalUrlEnv: getCanonicalUrl(),
        userType: request.auth.user?.user_type,
      });
      if (redirectTarget) {
        const redirectResponse = NextResponse.redirect(redirectTarget);
        redirectResponse.headers.set('x-pathname', redirectTarget.pathname);
        return redirectResponse;
      }

      // Prevent non-client users (internal) from accessing client portal
      const loginUrl = request.nextUrl.clone();
      loginUrl.pathname = '/auth/client-portal/signin';
      loginUrl.searchParams.set('error', 'AccessDenied');
      const redirectResponse = NextResponse.redirect(loginUrl);
      redirectResponse.headers.set('x-pathname', loginUrl.pathname);
      return redirectResponse;
    }
  }

  // Return the response with CORS headers and any i18n modifications
  return applyCorsHeaders(response, origin);
});

export default _middleware;
export { _middleware as middleware };

export const config = {
  matcher: [
    '/api/:path*',
    '/msp/:path*',
    '/client-portal/:path*',
    '/((?!_next/static|_next/image|favicon.ico|public/).*)',
  ]
};
