import { clerkMiddleware } from '@clerk/nextjs/server'
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'

// ─── IMPORTANT: Clerk domain configuration ────────────────────────────────
// Both app.meseoapp.com AND admin.meseoapp.com must be registered as
// allowed origins in the Clerk Dashboard:
//   Clerk Dashboard → Settings → Domains
// If admin.meseoapp.com is missing, browser requests to
// clerk.meseoapp.com/v1/client/sessions/... are blocked by CORS, which
// cascades into session-refresh failures and the admin panel losing
// authentication mid-session. The fix is purely a dashboard change —
// no code adjustment in this repo will resolve it.
// ──────────────────────────────────────────────────────────────────────────

// Tag INGEST endpoints — these accept unauthenticated POST requests from
// customer websites. They must never run through Clerk.
// Analytics/management endpoints (/api/site-tag, /api/site-tag/analytics,
// /api/site-tag/test-verify) are NOT in this list — they're called by the
// authenticated dashboard and need Clerk context.
const INGEST_PATHS = [
  '/api/public-audit',   // public lead-gen audit -- unauthenticated POST + tracking
  '/api/collect',
  '/api/collect/shopify', // Shopify Web Pixel ingest -- unauthenticated POST from checkout sandbox
  '/api/site-tag/event',
  '/api/site-tag/conversion',
  '/api/site-tag/heatmap',
  '/api/site-tag/rum',
  '/api/site-tag/page-seen',
  '/api/site-tag/page-links',
  '/api/site-tag/nav-structure',
  '/api/site-tag/seo-monitor',
  '/api/site-tag/issue-evidence',
  '/api/site-tag/audit-overlay',
  '/api/site-tag/schema',
  '/api/site-tag/script',
  '/api/site-tag/ai-session',
  '/t.js',
]

// audit.meseoapp.com hostname detection. In dev we also match
// audit.localhost so the public audit surface can be exercised locally
// with a hosts-file entry (127.0.0.1 audit.localhost).
function isAuditHost(host: string | null): boolean {
  if (!host) return false
  const lower = host.toLowerCase().split(':')[0]
  return lower === 'audit.meseoapp.com' || lower === 'audit.localhost'
}

// admin.meseoapp.com hostname detection. In dev we also match
// admin.localhost:* so the whole admin portal can be exercised
// against `next dev` with a hosts-file entry (127.0.0.1 admin.localhost).
//
// We deliberately match on the "admin." prefix rather than a hard-
// coded host list — this lets staging hosts (admin-staging.meseoapp.com,
// admin-pr-123.vercel.app) flow through the same routing without a
// config change.
function isAdminHost(host: string | null): boolean {
  if (!host) return false
  const lower = host.toLowerCase().split(':')[0] // strip :port
  if (lower === 'admin.meseoapp.com') return true
  if (lower === 'admin.localhost') return true
  if (lower.startsWith('admin-') && lower.endsWith('.meseoapp.com')) return true
  if (lower.startsWith('admin-') && lower.endsWith('.vercel.app')) return true
  return false
}

const APP_URL = process.env.NEXT_PUBLIC_APP_URL || 'https://app.meseoapp.com'
const DEBUG_MIDDLEWARE = process.env.DEBUG_MIDDLEWARE === '1'

// Paths that must NEVER get the /admin prefix on the admin host.
// Auth handshakes, Next.js internals, sign-in/up surfaces, the
// Clerk webhook ingress — these all need to resolve to their real
// path so Clerk can complete the session bootstrap before we route
// the user to the admin app.
const ADMIN_PASSTHROUGH_PREFIXES = [
  '/admin',          // already on the admin tree
  '/api/admin',      // ditto for admin APIs
  '/api',            // all other API routes resolve at their real path -- never rewrite /api/* to /admin/api/*
  '/sign-in',
  '/sign-up',
  '/api/auth',       // Clerk-controlled auth callbacks
  '/api/me',         // user/plan/profile endpoints (shared with main app)
  '/api/clerk',
  '/_next',
  '/favicon',
]
function shouldPassThroughOnAdminHost(path: string): boolean {
  return ADMIN_PASSTHROUGH_PREFIXES.some((p) => path === p || path.startsWith(p + '/'))
}

// Path-based admin-surface check. Doesn't need createRouteMatcher
// because we run it against effectivePath (post-rewrite) which is
// always the actual /admin or /api/admin route to gate.
function isAdminSurface(p: string): boolean {
  return p === '/admin' || p.startsWith('/admin/') || p.startsWith('/api/admin/') || p === '/api/admin'
}

// eslint-disable-next-line @typescript-eslint/no-explicit-any
const clerk = clerkMiddleware(async (auth: any, req: NextRequest) => {
  const host = req.headers.get('host')
  const path = req.nextUrl.pathname
  const adminHost = isAdminHost(host)

  // Diagnostic line — surfaces every request's hostname routing
  // decision in Vercel logs. Gated behind DEBUG_MIDDLEWARE=1 to
  // avoid log-drain volume on every ingest POST in production.
  if (DEBUG_MIDDLEWARE) console.info('[middleware] hostname:', host, 'isAdmin:', adminHost, 'path:', path)

  // ── Admin host: rewrite first, then auth-gate ─────────────────────
  // When a request lands on admin.meseoapp.com:
  //   1. If the path is already /admin/* or in the passthrough set
  //      (sign-in, _next, Clerk callbacks, /api/me/*, /api/admin/*),
  //      let it through as-is.
  //   2. Otherwise rewrite the URL so /<anything> → /admin/<anything>
  //      and / → /admin. The browser still sees the bare path; the
  //      Next router resolves the admin route.
  //   3. After the rewrite, run the superadmin gate against the
  //      effective (post-rewrite) path so /admin and /api/admin/*
  //      both get the same enforcement regardless of how the user
  //      arrived (hostname rewrite vs literal URL).
  let effectivePath = path
  let rewriteResponse: ReturnType<typeof NextResponse.rewrite> | null = null

  if (adminHost) {
    if (!shouldPassThroughOnAdminHost(path)) {
      const url = req.nextUrl.clone()
      url.pathname = path === '/' ? '/admin' : '/admin' + path
      effectivePath = url.pathname
      rewriteResponse = NextResponse.rewrite(url)
      if (DEBUG_MIDDLEWARE) console.info('[middleware] admin rewrite:', path, '→', url.pathname)
    }
  }

  // Auth gate runs on the effective (post-rewrite) path so a user
  // who hits admin.meseoapp.com/users gets gated as if they hit
  // /admin/users — middleware enforcement matches what the page
  // would see. Also fires on app.meseoapp.com/admin/* so a literal
  // admin URL on the main host gets the same gate.
  if (isAdminSurface(effectivePath)) {
    const authed = await auth()
    const { userId, sessionClaims } = authed

    // ── Diagnostic: full auth payload dump ─────────────────────────
    // Temporary instrumentation to debug "publicMetadata.role set to
    // superadmin in Clerk dashboard but admin gate still denies".
    // The two common causes:
    //   1. publicMetadata isn't included in Clerk's session-token
    //      template, so sessionClaims doesn't carry it. Fix: in the
    //      Clerk dashboard → Configure → Sessions → "Customize session
    //      token", add `{ "metadata": "{{user.public_metadata}}" }`
    //      (or `"publicMetadata": ...`) and the role lands in
    //      sessionClaims.metadata.role / .publicMetadata.role.
    //   2. The active session was issued before the metadata change
    //      and the user hasn't refreshed it. Sign out + back in to
    //      mint a new token.
    // Gated behind DEBUG_MIDDLEWARE=1 -- JSON.stringify(sessionClaims)
    // on every admin request was a significant log-drain cost.
    if (DEBUG_MIDDLEWARE) console.info('[middleware:admin-gate] FULL AUTH DEBUG:', {
      userId,
      sessionClaims: JSON.stringify(sessionClaims),
      // orgRole / orgId only exist when Clerk Organizations are
      // enabled. Logging both so we can rule them out as the source.
      orgRole: (authed as { orgRole?: string }).orgRole ?? null,
      orgId: (authed as { orgId?: string }).orgId ?? null,
      // Direct readouts of the two metadata buckets so the value
      // jumps out of the JSON wall above.
      metadataKey:       (sessionClaims?.metadata as Record<string, unknown> | undefined) ?? null,
      publicMetadataKey: (sessionClaims?.publicMetadata as Record<string, unknown> | undefined) ?? null,
    })

    if (!userId) {
      // Unauthenticated admin-host visitors land on sign-in. Preserve
      // the original URL (with the user-visible host + path, NOT the
      // rewritten /admin/* path) so post-sign-in redirect lands them
      // back where they intended.
      const signIn = new URL('/sign-in', req.url)
      signIn.searchParams.set('redirect_url', req.nextUrl.href)
      return NextResponse.redirect(signIn)
    }
    // Role check. Clerk puts publicMetadata under sessionClaims in
    // the JWT; we accept either a literal "superadmin" role marker
    // or a publicMetadata.isSuperadmin boolean.
    const role = (sessionClaims?.metadata as { role?: string } | undefined)?.role
      ?? (sessionClaims?.publicMetadata as { role?: string } | undefined)?.role
    const isSuperadmin = role === 'superadmin'
      || (sessionClaims?.metadata as { isSuperadmin?: boolean } | undefined)?.isSuperadmin === true
      || (sessionClaims?.publicMetadata as { isSuperadmin?: boolean } | undefined)?.isSuperadmin === true

    // ── Diagnostic: gate decision ──────────────────────────────────
    if (DEBUG_MIDDLEWARE) console.info('[middleware:admin-gate] decision:', {
      effectivePath,
      role,
      isSuperadmin,
    })

    if (!isSuperadmin) {
      // API responses need a 403 JSON body so client-side code can
      // display a useful error; page routes bounce to the main app
      // with an unauthorized flag so they know why they were kicked.
      if (effectivePath.startsWith('/api/')) {
        return NextResponse.json(
          { error: 'Forbidden', message: 'Superadmin role required.' },
          { status: 403 },
        )
      }
      const bounce = new URL(APP_URL)
      bounce.searchParams.set('admin_denied', '1')
      return NextResponse.redirect(bounce)
    }
  }

  // If we built a rewrite response above and didn't bounce, return
  // it so the rewritten path is the one the Next router resolves.
  if (rewriteResponse) return rewriteResponse
}, {
  // Allow the admin hostname to share the same Clerk session cookie as
  // the app hostname. Both subdomains of meseoapp.com see the session.
})

export default function middleware(req: NextRequest, event: Parameters<typeof clerk>[1]) {
  const path = req.nextUrl.pathname
  const host = req.headers.get('host')

  // 1. Ingest passthrough — always first; no auth, no host-based rewrite.
  //    Catches /api/public-audit/* from any hostname (including audit subdomain)
  //    before any other logic runs.
  if (INGEST_PATHS.some((p) => path === p || path.startsWith(p + '/'))) {
    if (DEBUG_MIDDLEWARE) console.log('[middleware] ingest passthrough:', req.method, path)
    return NextResponse.next()
  }

  // 2. Audit subdomain — bypass Clerk entirely, rewrite bare paths to /audit/*.
  //    audit.meseoapp.com/         → /audit
  //    audit.meseoapp.com/{id}     → /audit/{id}
  //    /api/* paths pass through unmodified (handled above or fall through).
  if (isAuditHost(host)) {
    if (!path.startsWith('/api/')) {
      const url = req.nextUrl.clone()
      url.pathname = path === '/' ? '/audit' : '/audit' + path
      if (DEBUG_MIDDLEWARE) console.log('[middleware] audit rewrite:', path, '→', url.pathname)
      return NextResponse.rewrite(url)
    }
    return NextResponse.next()
  }

  // 3. Block /audit/* on non-audit hosts — redirect to the subdomain so the
  //    public surface is always served from audit.meseoapp.com.
  if (path === '/audit' || path.startsWith('/audit/')) {
    const tail = path.slice('/audit'.length) || '/'
    return NextResponse.redirect(`https://audit.meseoapp.com${tail}`)
  }

  // 4. Everything else (including /api/site-tag/analytics and /api/site-tag
  //    management endpoints) gets the Clerk auth context.
  return clerk(req, event)
}

export const config = {
  // Exclude static assets and ingest beacon endpoints from middleware entirely.
  // Ingest paths are public and unauthenticated -- running Clerk/edge middleware
  // on them costs ~22M no-op invocations per 12h. The body-level INGEST_PATHS
  // passthrough below remains as defense-in-depth but the matcher is the
  // primary gate.
  //
  // Rewrite paths (audit subdomain, ~30K) and redirect paths (/audit/* on
  // main host, ~512) are page routes -- they are NOT excluded here and
  // continue to hit the middleware body as before.
  matcher: [
    '/((?!_next|auth/|t\\.js|api/collect|api/public-audit|api/site-tag/event|api/site-tag/conversion|api/site-tag/heatmap|api/site-tag/rum|api/site-tag/page-seen|api/site-tag/page-links|api/site-tag/nav-structure|api/site-tag/seo-monitor|api/site-tag/issue-evidence|api/site-tag/audit-overlay|api/site-tag/schema|api/site-tag/script|api/site-tag/ai-session|[^?]*\\.(?:html?|css|js(?!on)|jpe?g|webp|png|gif|svg|ttf|woff2?|ico|csv|docx?|xlsx?|zip|webmanifest)).*)',
  ],
}
