/**
 * Tenancies module gate. The entire Tenancies surface (sidebar entry,
 * entity tabs, top-level page, dashboard widgets, webhook auto-create
 * branch) is hidden by default and only surfaces when EITHER:
 *
 *   (a) at least one `berth_tenancies` row exists for the port
 *       (lazy auto-enable on first creation), OR
 *   (b) an admin has explicitly enabled the module via
 *       `system_settings.tenancies_module_enabled` (default false).
 *
 * Per the locked decision (2026-05-25): a sold berth stays sold without
 * any tenancy data — the platform does not assume tenancies exist for
 * sold berths. The module only surfaces when one of the two conditions
 * above is true.
 *
 * See `docs/tenancies-design.md` for the full rationale + UX flow.
 */

import { and, eq, isNull, or } from 'drizzle-orm';

import { db } from '@/lib/db';
import { systemSettings } from '@/lib/db/schema/system';
import { berthTenancies } from '@/lib/db/schema/tenancies';
import { NotFoundError } from '@/lib/errors';

/**
 * Resolve whether the Tenancies module is currently active for the
 * given port. Honors both the explicit admin setting AND the lazy
 * "any row exists" sentinel.
 *
 * Performance: two indexed reads; the row-exists check uses `LIMIT 1`
 * so it stops at the first match. Cached at the service boundary via
 * react-query in the UI; never called inside tight loops on the server.
 */
export async function isTenanciesModuleEnabled(portId: string): Promise<boolean> {
  // 1. Admin setting check (port-scoped row first, fall back to global).
  //    Precedence: an EXPLICIT admin choice always wins. If the admin has
  //    set the toggle to true, the module is on. If they've set it to
  //    false, the module is off - even if tenancy rows exist for the
  //    port. This matches the toggle's label ("Tenancies module - off")
  //    matching what reps see in the sidebar; the previous behaviour of
  //    silently re-enabling whenever any row existed was confusing and
  //    contradicted the toggle's own description.
  const settingRow = await db
    .select({ value: systemSettings.value })
    .from(systemSettings)
    .where(
      and(
        eq(systemSettings.key, 'tenancies_module_enabled'),
        or(eq(systemSettings.portId, portId), isNull(systemSettings.portId)),
      ),
    )
    .limit(1);
  const stored = settingRow[0]?.value;
  if (stored === true) return true;
  if (stored === false) return false;

  // 2. No explicit admin choice yet: lazy auto-enable on first row. Once
  //    a port has at least one tenancy, the module's UX is justified and
  //    we surface it without making the admin toggle it manually. The
  //    admin can still flip it off afterwards via the toggle (which
  //    writes false and short-circuits this branch above).
  const rowCheck = await db
    .select({ id: berthTenancies.id })
    .from(berthTenancies)
    .where(eq(berthTenancies.portId, portId))
    .limit(1);
  return rowCheck.length > 0;
}

/**
 * Idempotent helper for the lazy auto-create branch + admin-driven
 * enables to call. Inserts/updates the system_settings row to true.
 * Safe to call when already enabled (UPSERT on key+port).
 *
 * Precedence guard: an EXPLICIT admin `false` always wins. If a port-
 * scoped (or global) `tenancies_module_enabled` row is stored as
 * `false`, this is a NO-OP — auto-create paths (e.g. `createPending`
 * minting the first-ever tenancy) must never silently flip a module the
 * admin deliberately disabled back on. When the setting is UNSET, the
 * lazy "first row surfaces the module" behaviour is preserved: the
 * UPSERT writes `true` so the module appears without manual toggling.
 */
export async function enableTenanciesModule(portId: string): Promise<void> {
  // Respect an explicit stored `false` (port-scoped row first, then the
  // global fallback). Only an UNSET setting auto-enables.
  const settingRow = await db
    .select({ value: systemSettings.value })
    .from(systemSettings)
    .where(
      and(
        eq(systemSettings.key, 'tenancies_module_enabled'),
        or(eq(systemSettings.portId, portId), isNull(systemSettings.portId)),
      ),
    )
    .limit(1);
  if (settingRow[0]?.value === false) return;

  await db
    .insert(systemSettings)
    .values({
      key: 'tenancies_module_enabled',
      portId,
      value: true,
    })
    .onConflictDoUpdate({
      target: [systemSettings.key, systemSettings.portId],
      set: { value: true, updatedAt: new Date() },
    });
}

/**
 * Admin-driven disable. Does NOT delete any tenancies rows — those
 * remain in the database and surface again when the module is re-enabled.
 * The frontend warns the operator about the row count before calling
 * this, so by the time it lands here the intent is confirmed.
 */
export async function disableTenanciesModule(portId: string): Promise<void> {
  await db
    .insert(systemSettings)
    .values({
      key: 'tenancies_module_enabled',
      portId,
      value: false,
    })
    .onConflictDoUpdate({
      target: [systemSettings.key, systemSettings.portId],
      set: { value: false, updatedAt: new Date() },
    });
}

/**
 * Convenience throw-on-disabled helper for route handlers and services
 * that should hard-fail (404 / NotFound) when the module is off. Most
 * Tenancies-scoped routes wrap their handler body in this.
 */
export async function assertTenanciesModuleEnabled(portId: string): Promise<void> {
  const enabled = await isTenanciesModuleEnabled(portId);
  if (!enabled) {
    // Route handlers map NotFoundError to 404; the sidebar + entity tabs
    // independently gate so the rep never sees a link that would 404.
    throw new NotFoundError('Tenancies module is not enabled for this port.');
  }
}
