/**
 * Server-side onboarding status resolver. Shared by the admin checklist
 * page, the topbar discoverability banner, and the dashboard onboarding
 * tile so all three surfaces always agree on what's "done."
 *
 * Steps + auto-check rules live here (single source of truth); the UI
 * surfaces consume the resolved status via the `/api/v1/admin/onboarding/status`
 * endpoint.
 */

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

import { db } from '@/lib/db';
import { systemSettings } from '@/lib/db/schema/system';
import { resolveForAdminAPI } from '@/lib/settings/resolver';

export interface OnboardingStep {
  id: string;
  href: string;
  label: string;
  description: string;
  autoCheckSettingKey?: string;
  autoCheckSettingKeysAll?: readonly string[];
  /** When set, the step is marked done when the named list endpoint returns
   *  at least one row. The endpoint URL is read by the client only;
   *  server-side resolver treats these as manual-only. */
  autoCheckListEndpoint?: string;
}

export const ONBOARDING_STEPS: readonly OnboardingStep[] = [
  {
    id: 'branding',
    href: 'branding',
    label: 'Set port name, logo, primary colour',
    description: 'Branding flows into the navbar, emails, EOI PDFs, and the public auth shell.',
    autoCheckSettingKey: 'branding_logo_url',
  },
  {
    id: 'email',
    href: 'email',
    label: 'Configure outgoing email',
    description:
      'From-address, signature, footer, plus per-port SMTP overrides if you don’t use the global account.',
    autoCheckSettingKey: 'smtp_host_override',
  },
  {
    id: 'documenso',
    href: 'documenso',
    label: 'Connect Documenso for EOIs',
    description:
      'API credentials, the EOI template id, plus the developer + approver identity that signs every EOI.',
    autoCheckSettingKeysAll: [
      'documenso_api_url_override',
      'documenso_developer_email',
      'documenso_approver_email',
      'documenso_eoi_template_id',
    ],
  },
  {
    id: 'settings',
    href: 'settings',
    label: 'Tune business rules + recommender weights',
    description:
      'Pipeline weights, net-10 discount, berth recommender knobs (heat weights, fall-through policy).',
    autoCheckSettingKey: 'heat_weight_recency',
  },
  {
    id: 'roles',
    href: 'roles',
    label: 'Create roles & assign users',
    description: 'Per-port roles inherit from system roles; override permissions here.',
    autoCheckListEndpoint: '/api/v1/admin/roles',
  },
  {
    id: 'users',
    href: 'users',
    label: 'Invite the rest of the team',
    description:
      'Invite users, assign roles, optionally grant residential access. Track pending vs accepted.',
    autoCheckListEndpoint: '/api/v1/admin/users',
  },
  {
    id: 'tags',
    href: 'tags',
    label: 'Define starter tags',
    description: 'Color-coded labels used across clients, yachts, companies, and interests.',
    autoCheckListEndpoint: '/api/v1/tags/options',
  },
  {
    id: 'storage',
    href: 'storage',
    label: 'Configure storage backend',
    description:
      'Verify S3/filesystem and run a test connection before going live so PDFs and avatars persist correctly.',
    autoCheckSettingKey: 'storage_backend',
  },
  {
    id: 'forms',
    href: 'forms',
    label: 'Wire the website intake forms',
    description:
      'Inquiry forms on the marketing site dual-write into the CRM via /api/public/website-inquiries. Manually mark complete when verified.',
  },
];

export interface OnboardingStatus {
  steps: Array<OnboardingStep & { done: boolean; auto: boolean }>;
  completed: number;
  total: number;
  percent: number;
  isComplete: boolean;
  /** The next undone step the admin should tackle. Null when complete. */
  nextStep: (OnboardingStep & { done: false }) | null;
}

/**
 * Resolves onboarding status for the given port. Auto-checks read the full
 * setting chain (port → global → env → default) via `resolveSettings`;
 * list-endpoint checks are treated as not-auto-resolvable server-side and
 * fall back to the manual-checkbox state in `onboarding_manual_status`.
 */
export async function resolveOnboardingStatus(portId: string): Promise<OnboardingStatus> {
  const keys = new Set<string>();
  for (const s of ONBOARDING_STEPS) {
    if (s.autoCheckSettingKey) keys.add(s.autoCheckSettingKey);
    if (s.autoCheckSettingKeysAll) for (const k of s.autoCheckSettingKeysAll) keys.add(k);
  }

  const resolved =
    keys.size > 0
      ? await resolveForAdminAPI(Array.from(keys), portId)
      : new Map<string, { isSet: boolean }>();

  // Manual-checkbox state lives in a JSON blob row outside the registry.
  // Same lookup pattern as the admin page: port-scoped row first, fall
  // back to a global one when the port hasn't written its own.
  const manualRow = await db
    .select({ value: systemSettings.value })
    .from(systemSettings)
    .where(
      and(
        eq(systemSettings.key, 'onboarding_manual_status'),
        or(eq(systemSettings.portId, portId), isNull(systemSettings.portId)),
      ),
    )
    .limit(1);
  const manual = (manualRow[0]?.value ?? {}) as Record<string, boolean>;

  let firstUndone: (OnboardingStep & { done: false }) | null = null;
  const steps = ONBOARDING_STEPS.map((step) => {
    let autoDone = false;
    if (step.autoCheckSettingKey) {
      autoDone = Boolean(resolved.get(step.autoCheckSettingKey)?.isSet);
    } else if (step.autoCheckSettingKeysAll) {
      autoDone = step.autoCheckSettingKeysAll.every((k) => Boolean(resolved.get(k)?.isSet));
    }
    const manualDone = Boolean(manual[step.id]);
    const done = autoDone || manualDone;
    if (!done && !firstUndone) firstUndone = { ...step, done: false };
    return { ...step, done, auto: autoDone };
  });

  const completed = steps.filter((s) => s.done).length;
  const total = steps.length;
  const percent = total > 0 ? Math.round((completed / total) * 100) : 0;
  return {
    steps,
    completed,
    total,
    percent,
    isComplete: completed === total,
    nextStep: firstUndone,
  };
}
