import { and, eq } from 'drizzle-orm';
import { z } from 'zod';

import { db } from '@/lib/db';
import { berths } from '@/lib/db/schema/berths';
import { createBerth, updateBerth } from '@/lib/services/berths.service';

import type { ImportAdapter, MappedRow } from '../types';

/**
 * Accepted import spellings of a mooring: letters, an optional separating
 * hyphen, optional leading zeros, then 1–6 digits. The 6-digit cap (audit
 * L33(b)) rejects absurd numbers that would overflow JS's safe-integer range
 * during canonicalization — a real marina mooring is at most a few thousand.
 * Canonicalization strips the hyphen + leading zeros and upper-cases the
 * letters, so the *output* always conforms to the canonical `^[A-Z]+\d+$`.
 */
const MOORING_INPUT_RE = /^[A-Za-z]+-?0*\d{1,6}$/;
/** Canonical stored form — the post-canonicalization invariant. */
const MOORING_CANON_RE = /^[A-Z]+\d+$/;

/** Canonicalize a mooring to the unified `^[A-Z]+\d+$` form ("A1", "D32"):
 *  uppercase letters, drop a hyphen + leading zeros on the number. The number
 *  is normalized digit-wise (no parseInt) so values up to the 6-digit input
 *  cap survive without floating-point/MAX_SAFE_INTEGER precision loss. */
function canonMoo(raw: string): string {
  const m = /^([A-Za-z]+)-?0*(\d+)$/.exec(raw.trim());
  if (!m) return raw.trim().toUpperCase();
  // Drop leading zeros without parseInt; keep a lone "0" as "0".
  const digits = m[2]!.replace(/^0+(?=\d)/, '');
  return `${m[1]!.toUpperCase()}${digits}`;
}

const num = (s: string | undefined): number | undefined =>
  s === undefined || s === '' ? undefined : Number(s);

export const berthsAdapter: ImportAdapter = {
  key: 'berths',
  label: 'Berths',
  order: 4,
  dependsOn: [],
  targetFields: [
    {
      key: 'mooringNumber',
      label: 'Mooring number',
      required: true,
      aliases: ['mooring', 'berth', 'berthnumber'],
      zod: z
        .string()
        .regex(MOORING_INPUT_RE, 'Use a form like A1, B12, E18 (max 6 digits)')
        // Defense in depth: whatever the input spelling, the canonical form
        // must conform to ^[A-Z]+\d+$ (audit L33(b)).
        .refine((v) => MOORING_CANON_RE.test(canonMoo(v)), 'Invalid mooring format'),
    },
    {
      key: 'area',
      label: 'Area',
      required: true,
      aliases: ['dock', 'zone'],
      zod: z.string().min(1),
    },
    { key: 'lengthFt', label: 'Length (ft)', required: false, zod: z.coerce.number() },
    { key: 'widthFt', label: 'Width (ft)', required: false, zod: z.coerce.number() },
    { key: 'draftFt', label: 'Draft (ft)', required: false, zod: z.coerce.number() },
    { key: 'price', label: 'Price', required: false, zod: z.coerce.number() },
    {
      key: 'priceCurrency',
      label: 'Currency',
      required: false,
      zod: z.string().length(3),
    },
    {
      key: 'status',
      label: 'Status',
      required: false,
      zod: z.enum(['available', 'under_offer', 'sold']),
    },
  ],
  matchKey: (row) => (row.mooringNumber ? canonMoo(row.mooringNumber) : null),
  findExisting: async (portId, key) => {
    const row = await db.query.berths.findFirst({
      where: and(eq(berths.portId, portId), eq(berths.mooringNumber, key)),
      columns: { id: true },
    });
    return row ?? null;
  },
  insert: async (row, _resolved, ctx) => {
    const b = await createBerth(ctx.portId, buildBerthInput(row), ctx.meta);
    return { id: b.id };
  },
  update: async (id, row, _resolved, ctx) => {
    const full = buildBerthInput(row);
    // Update spec fields only — mooring is the match key, and status has its
    // own dedicated endpoint (not part of UpdateBerthInput).
    await updateBerth(
      id,
      ctx.portId,
      {
        area: full.area,
        lengthFt: full.lengthFt,
        widthFt: full.widthFt,
        draftFt: full.draftFt,
        price: full.price,
        priceCurrency: full.priceCurrency,
      },
      ctx.meta,
    );
  },
};

function buildBerthInput(row: MappedRow) {
  return {
    mooringNumber: canonMoo(row.mooringNumber!),
    area: row.area!, // required field — present after validation
    lengthFt: num(row.lengthFt),
    widthFt: num(row.widthFt),
    draftFt: num(row.draftFt),
    price: num(row.price),
    priceCurrency: row.priceCurrency,
    status: (row.status as 'available' | 'under_offer' | 'sold' | undefined) ?? 'available',
  };
}
