/**
 * Inquiries workbench service — read/triage/convert over `website_submissions`.
 *
 * The capture endpoint (`/api/public/website-inquiries`) writes raw submissions;
 * this service is the operator-facing layer: list/filter, triage state changes,
 * and converting an inquiry into proper CRM entities (client and/or interest)
 * with the submission row linked back to what it produced.
 */

import { and, eq, inArray, isNull, sql, type SQL } from 'drizzle-orm';

import { db } from '@/lib/db';
import { websiteSubmissions } from '@/lib/db/schema/website-submissions';
import { clients, clientContacts } from '@/lib/db/schema/clients';
import { interests } from '@/lib/db/schema/interests';
import { buildListQuery } from '@/lib/db/query-builder';
import { createAuditLog, type AuditMeta } from '@/lib/audit';
import { ConflictError, NotFoundError } from '@/lib/errors';
import { createClient } from './clients.service';
import { createInterest } from './interests.service';
import { extractInquiryFields } from './website-intake-fields';
import { createClientSchema } from '@/lib/validators/clients';
import { createInterestSchema } from '@/lib/validators/interests';
import type { ListInquiriesInput } from '@/lib/validators/inquiries';

type TriageState = 'open' | 'assigned' | 'converted' | 'dismissed';

const SORTABLE = {
  receivedAt: websiteSubmissions.receivedAt,
  kind: websiteSubmissions.kind,
  triageState: websiteSubmissions.triageState,
  contactName: websiteSubmissions.contactName,
} as const;

export async function listInquiries(portId: string, query: ListInquiriesInput) {
  const filters: SQL[] = [];
  if (query.kind) filters.push(eq(websiteSubmissions.kind, query.kind));
  if (query.state === 'inbox') {
    filters.push(inArray(websiteSubmissions.triageState, ['open', 'assigned']));
  } else if (query.state !== 'all') {
    filters.push(eq(websiteSubmissions.triageState, query.state));
  }

  const sortColumn =
    query.sort && query.sort in SORTABLE
      ? SORTABLE[query.sort as keyof typeof SORTABLE]
      : undefined;

  return buildListQuery<typeof websiteSubmissions.$inferSelect>({
    table: websiteSubmissions,
    portIdColumn: websiteSubmissions.portId,
    portId,
    idColumn: websiteSubmissions.id,
    // website_submissions has no updatedAt; receivedAt is the natural clock and
    // the deterministic tail-sort.
    updatedAtColumn: websiteSubmissions.receivedAt,
    searchColumns: [websiteSubmissions.contactName, websiteSubmissions.contactEmail],
    searchTerm: query.search,
    filters,
    sort: sortColumn ? { column: sortColumn, direction: query.order } : undefined,
    page: query.page,
    pageSize: query.limit,
  });
}

export async function getInquiryById(id: string, portId: string) {
  const row = await loadInquiry(id, portId);

  const convertedClient = row.convertedClientId
    ? ((
        await db
          .select({ id: clients.id, fullName: clients.fullName })
          .from(clients)
          .where(eq(clients.id, row.convertedClientId))
          .limit(1)
      )[0] ?? null)
    : null;

  const convertedInterest = row.convertedInterestId
    ? ((
        await db
          .select({ id: interests.id, pipelineStage: interests.pipelineStage })
          .from(interests)
          .where(eq(interests.id, row.convertedInterestId))
          .limit(1)
      )[0] ?? null)
    : null;

  return { ...row, convertedClient, convertedInterest };
}

export async function triageInquiry(
  id: string,
  portId: string,
  state: TriageState,
  meta: AuditMeta,
) {
  const [updated] = await db
    .update(websiteSubmissions)
    .set({ triageState: state, triagedAt: new Date(), triagedBy: meta.userId })
    .where(and(eq(websiteSubmissions.id, id), eq(websiteSubmissions.portId, portId)))
    .returning();
  if (!updated) throw new NotFoundError('inquiry');

  void createAuditLog({
    userId: meta.userId,
    portId,
    action: 'update',
    entityType: 'website_submission',
    entityId: id,
    fieldChanged: 'triageState',
    newValue: { triageState: state },
    ipAddress: meta.ipAddress,
    userAgent: meta.userAgent,
  });
  return updated;
}

export async function convertInquiryToClient(id: string, portId: string, meta: AuditMeta) {
  const row = await loadInquiry(id, portId);
  // Idempotent: if already linked to a client, return it rather than duplicate.
  if (row.convertedClientId) return { clientId: row.convertedClientId, interestId: null };

  const clientId = await findOrCreateClientFromInquiry(row, meta);
  await markConverted(id, portId, { clientId }, meta);
  return { clientId, interestId: null };
}

export async function convertInquiryToInterest(id: string, portId: string, meta: AuditMeta) {
  const row = await loadInquiry(id, portId);
  if (row.convertedInterestId) {
    throw new ConflictError('Inquiry has already been converted to an interest.');
  }

  const clientId = row.convertedClientId ?? (await findOrCreateClientFromInquiry(row, meta));

  const interestData = createInterestSchema.parse({
    clientId,
    pipelineStage: 'enquiry',
    source: 'website',
  });
  const interest = await createInterest(portId, interestData, meta);

  await markConverted(id, portId, { clientId, interestId: interest.id }, meta);
  return { clientId, interestId: interest.id };
}

// ─── internals ────────────────────────────────────────────────────────────────

async function loadInquiry(id: string, portId: string) {
  const [row] = await db
    .select()
    .from(websiteSubmissions)
    .where(and(eq(websiteSubmissions.id, id), eq(websiteSubmissions.portId, portId)))
    .limit(1);
  if (!row) throw new NotFoundError('inquiry');
  return row;
}

/**
 * Find a single in-port client whose email contact matches the inquiry's email,
 * else create a new client from the payload. Returns the client id.
 */
async function findOrCreateClientFromInquiry(
  row: typeof websiteSubmissions.$inferSelect,
  meta: AuditMeta,
): Promise<string> {
  const fields = extractInquiryFields((row.payload ?? {}) as Record<string, unknown>);
  const email = (row.contactEmail ?? fields.email ?? '').trim();

  if (email) {
    const matches = await db
      .selectDistinct({ id: clients.id })
      .from(clients)
      .innerJoin(clientContacts, eq(clientContacts.clientId, clients.id))
      .where(
        and(
          eq(clients.portId, meta.portId),
          isNull(clients.archivedAt),
          eq(clientContacts.channel, 'email'),
          sql`lower(${clientContacts.value}) = ${email.toLowerCase()}`,
        ),
      )
      .limit(2);
    // Only auto-link on an unambiguous single match.
    if (matches.length === 1) return matches[0]!.id;
  }

  const contacts: Array<{
    channel: 'email' | 'phone' | 'other';
    value: string;
    isPrimary?: boolean;
  }> = [];
  if (email) contacts.push({ channel: 'email', value: email, isPrimary: true });
  const phone = (fields.phone ?? '').trim();
  if (phone) contacts.push({ channel: 'phone', value: phone });
  if (contacts.length === 0) {
    // Schema requires ≥1 contact; fall back to a name-bearing "other" contact.
    contacts.push({ channel: 'other', value: row.contactName ?? 'Website inquiry' });
  }

  const fullName = (row.contactName ?? fields.fullName ?? email ?? '').trim() || 'Website inquiry';

  const clientData = createClientSchema.parse({
    fullName,
    contacts,
    source: 'website',
    sourceInquiryId: row.id,
  });
  const client = await createClient(meta.portId, clientData, meta);
  return client.id;
}

async function markConverted(
  id: string,
  portId: string,
  refs: { clientId: string; interestId?: string },
  meta: AuditMeta,
) {
  await db
    .update(websiteSubmissions)
    .set({
      convertedClientId: refs.clientId,
      ...(refs.interestId ? { convertedInterestId: refs.interestId } : {}),
      triageState: 'converted',
      triagedAt: new Date(),
      triagedBy: meta.userId,
    })
    .where(and(eq(websiteSubmissions.id, id), eq(websiteSubmissions.portId, portId)));

  void createAuditLog({
    userId: meta.userId,
    portId,
    action: 'update',
    entityType: 'website_submission',
    entityId: id,
    fieldChanged: 'triageState',
    newValue: { triageState: 'converted', ...refs },
    ipAddress: meta.ipAddress,
    userAgent: meta.userAgent,
  });
}
