import { redirect } from 'next/navigation';
import { cookies, headers } from 'next/headers';
import { eq } from 'drizzle-orm';

import { auth } from '@/lib/auth';
import { db } from '@/lib/db';
import { ports as portsTable } from '@/lib/db/schema/ports';
import { userPortRoles, userProfiles } from '@/lib/db/schema/users';
import { QueryProvider } from '@/providers/query-provider';
import { SocketProvider } from '@/providers/socket-provider';
import { PortProvider } from '@/providers/port-provider';
import { PermissionsProvider } from '@/providers/permissions-provider';
import { AppShell } from '@/components/layout/app-shell';
import { OnboardingBanner } from '@/components/admin/onboarding-banner';
import { DevModeBanner } from '@/components/shared/dev-mode-banner';
import { RealtimeToasts } from '@/components/shared/realtime-toasts';
import { WebVitalsReporter } from '@/components/shared/web-vitals-reporter';
import { classifyFormFactor } from '@/lib/form-factor';
import { getPortBrandingConfig } from '@/lib/services/port-config';
import { isTenanciesModuleEnabled } from '@/lib/services/tenancies-module.service';
import { isExpensesModuleEnabled } from '@/lib/services/expenses-module.service';
import { isResidentialModuleEnabled } from '@/lib/services/residential-module.service';
import { isMaintenanceModuleEnabled } from '@/lib/services/maintenance-module.service';

export default async function DashboardLayout({ children }: { children: React.ReactNode }) {
  const headerList = await headers();
  const session = await auth.api.getSession({ headers: headerList });
  if (!session?.user) redirect('/login');

  // Super admins have implicit access to every port; everyone else only sees
  // ports they have an explicit user_port_roles row for.
  const profile = await db.query.userProfiles.findFirst({
    where: eq(userProfiles.userId, session.user.id),
  });

  const portRoles = await db.query.userPortRoles.findMany({
    where: eq(userPortRoles.userId, session.user.id),
    with: { port: true, role: true },
  });

  const ports = profile?.isSuperAdmin
    ? await db.query.ports.findMany({ orderBy: portsTable.createdAt })
    : portRoles.map((pr) => pr.port);

  // Prefer a previously-resolved tier from the client's cookie so the
  // server renders the matching shell on first paint - eliminates the
  // mobile↔desktop chrome flicker that happens when UA-based classification
  // disagrees with the actual viewport (e.g. macOS Safari with the
  // window dragged below 1024). AppShell writes the cookie after the
  // first matchMedia evaluation; subsequent reloads use the hint.
  const cookieStore = await cookies();
  const tierCookie = cookieStore.get('pn-crm.viewport-tier')?.value;
  const initialFormFactor: 'mobile' | 'desktop' =
    tierCookie === 'mobile'
      ? 'mobile'
      : tierCookie === 'tablet' || tierCookie === 'desktop'
        ? 'desktop'
        : classifyFormFactor(headerList.get('user-agent'));
  const user = {
    name: profile?.displayName ?? session.user.name ?? session.user.email,
    email: session.user.email,
  };

  // Per-port logo map for the sidebar. Resolved server-side so the
  // sidebar can swap brand on port switch without an extra round-trip.
  // Falls back to null per port when no logo is configured - the
  // sidebar surfaces nothing rather than leaking a generic placeholder.
  const portBrandingEntries = await Promise.all(
    ports.map(async (p) => {
      try {
        const cfg = await getPortBrandingConfig(p.id);
        return [p.id, cfg.logoUrl] as const;
      } catch {
        return [p.id, null] as const;
      }
    }),
  );
  const portLogoUrls: Record<string, string | null> = Object.fromEntries(portBrandingEntries);

  // Per-port tenancies-module gate. Hidden by default; flips on either by
  // the admin switch (Operations) OR the lazy auto-enable on first row.
  // Resolved server-side so the sidebar nav SSRs in/out atomically with
  // the layout instead of flickering after a client-side fetch.
  const tenanciesModuleEntries = await Promise.all(
    ports.map(async (p) => {
      try {
        return [p.id, await isTenanciesModuleEnabled(p.id)] as const;
      } catch {
        return [p.id, false] as const;
      }
    }),
  );
  const tenanciesModuleByPort: Record<string, boolean> = Object.fromEntries(tenanciesModuleEntries);

  // Per-port expenses-module gate. Defaults to enabled (the registry's
  // default) so existing ports keep the feature on deploy. Resolved
  // server-side so the sidebar SSRs without flicker when an admin has
  // turned the feature off for a tenant.
  const expensesModuleEntries = await Promise.all(
    ports.map(async (p) => {
      try {
        return [p.id, await isExpensesModuleEnabled(p.id)] as const;
      } catch {
        // Conservative default on lookup failure: keep the feature
        // visible so a transient DB hiccup doesn't hide the module
        // for a port that actually has it enabled.
        return [p.id, true] as const;
      }
    }),
  );
  const expensesModuleByPort: Record<string, boolean> = Object.fromEntries(expensesModuleEntries);

  // Per-port residential-module gate. Defaults to enabled (the registry's
  // default) so existing ports keep the feature on deploy. Resolved
  // server-side so the sidebar "Residential" section SSRs in/out without
  // flicker when an admin has turned the feature off for a tenant.
  const residentialModuleEntries = await Promise.all(
    ports.map(async (p) => {
      try {
        return [p.id, await isResidentialModuleEnabled(p.id)] as const;
      } catch {
        // Conservative default on lookup failure: keep the feature
        // visible so a transient DB hiccup doesn't hide the module.
        return [p.id, true] as const;
      }
    }),
  );
  const residentialModuleByPort: Record<string, boolean> =
    Object.fromEntries(residentialModuleEntries);

  // Per-port maintenance-module gate. Defaults to enabled (registry
  // default) so existing ports keep the berth Maintenance tab on deploy.
  // Resolved server-side so the tab SSRs in/out without flicker.
  const maintenanceModuleEntries = await Promise.all(
    ports.map(async (p) => {
      try {
        return [p.id, await isMaintenanceModuleEnabled(p.id)] as const;
      } catch {
        // Conservative default on lookup failure: keep the feature visible.
        return [p.id, true] as const;
      }
    }),
  );
  const maintenanceModuleByPort: Record<string, boolean> =
    Object.fromEntries(maintenanceModuleEntries);

  return (
    <QueryProvider>
      <PortProvider
        ports={ports}
        defaultPortId={ports[0]?.id ?? null}
        tenanciesModuleByPort={tenanciesModuleByPort}
        maintenanceModuleByPort={maintenanceModuleByPort}
      >
        <PermissionsProvider>
          <SocketProvider>
            <RealtimeToasts />
            <WebVitalsReporter />
            {/* Sticky banner across the app whenever EMAIL_REDIRECT_TO is
                set so reps + admins always know outbound mail is being
                rerouted. Production hides itself (env.ts forbids the
                flag in prod) so the banner is dev/staging-only. */}
            <DevModeBanner />
            <OnboardingBanner />
            {/* #26: AppShell mounts ONE responsive tree (desktop OR
             *  mobile) per render - never both - so pages don't pay the
             *  double-state, double-fetch, double-Tabs-provider tax. */}
            <AppShell
              portRoles={portRoles}
              isSuperAdmin={profile?.isSuperAdmin ?? false}
              user={user}
              ports={ports}
              portLogoUrls={portLogoUrls}
              tenanciesModuleByPort={tenanciesModuleByPort}
              expensesModuleByPort={expensesModuleByPort}
              residentialModuleByPort={residentialModuleByPort}
              initialFormFactor={initialFormFactor}
            >
              {children}
            </AppShell>
          </SocketProvider>
        </PermissionsProvider>
      </PortProvider>
    </QueryProvider>
  );
}
