/**
 * CM-9: proxy / point-of-contact service.
 *
 * A proxy is a designated contact attached to a client, interest, or yacht
 * (one per entity). `resolveEffectiveProxy` picks the most specific for an
 * outbound-comms context via the chain yacht → interest → client. All
 * operations are port-scoped; the entity is verified to belong to the port.
 */

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

import { createAuditLog, toAuditJson, type AuditMeta } from '@/lib/audit';
import { db } from '@/lib/db';
import { clients, interests, proxies, yachts } from '@/lib/db/schema';
import type { Proxy } from '@/lib/db/schema';
import { NotFoundError } from '@/lib/errors';
import type { ProxyEntityType, SetProxyInput } from '@/lib/validators/proxies';

const norm = (v?: string | null): string | null => {
  const t = v?.trim();
  return t ? t : null;
};

async function assertEntityInPort(
  entityType: ProxyEntityType,
  entityId: string,
  portId: string,
): Promise<void> {
  let exists = false;
  if (entityType === 'client') {
    const [r] = await db
      .select({ id: clients.id })
      .from(clients)
      .where(and(eq(clients.id, entityId), eq(clients.portId, portId)))
      .limit(1);
    exists = !!r;
  } else if (entityType === 'interest') {
    const [r] = await db
      .select({ id: interests.id })
      .from(interests)
      .where(and(eq(interests.id, entityId), eq(interests.portId, portId)))
      .limit(1);
    exists = !!r;
  } else {
    const [r] = await db
      .select({ id: yachts.id })
      .from(yachts)
      .where(and(eq(yachts.id, entityId), eq(yachts.portId, portId)))
      .limit(1);
    exists = !!r;
  }
  if (!exists) throw new NotFoundError(`${entityType} not found in this port`);
}

export async function getProxy(
  portId: string,
  entityType: ProxyEntityType,
  entityId: string,
): Promise<Proxy | null> {
  const [row] = await db
    .select()
    .from(proxies)
    .where(
      and(
        eq(proxies.portId, portId),
        eq(proxies.entityType, entityType),
        eq(proxies.entityId, entityId),
      ),
    )
    .limit(1);
  return row ?? null;
}

export async function setProxy(
  portId: string,
  entityType: ProxyEntityType,
  entityId: string,
  data: SetProxyInput,
  meta: AuditMeta,
): Promise<Proxy> {
  await assertEntityInPort(entityType, entityId, portId);
  const next = {
    name: data.name.trim(),
    email: norm(data.email),
    phone: norm(data.phone),
    relationship: norm(data.relationship),
    notes: norm(data.notes),
    updatedAt: new Date(),
  };
  const [row] = await db
    .insert(proxies)
    .values({ portId, entityType, entityId, ...next })
    .onConflictDoUpdate({
      target: [proxies.portId, proxies.entityType, proxies.entityId],
      set: next,
    })
    .returning();
  if (!row) throw new NotFoundError('Failed to save proxy');
  void createAuditLog({
    ...meta,
    action: 'update',
    entityType: `proxy_${entityType}`,
    entityId,
    newValue: toAuditJson(next),
  });
  return row;
}

export async function clearProxy(
  portId: string,
  entityType: ProxyEntityType,
  entityId: string,
  meta: AuditMeta,
): Promise<void> {
  await db
    .delete(proxies)
    .where(
      and(
        eq(proxies.portId, portId),
        eq(proxies.entityType, entityType),
        eq(proxies.entityId, entityId),
      ),
    );
  void createAuditLog({
    ...meta,
    action: 'delete',
    entityType: `proxy_${entityType}`,
    entityId,
  });
}

export interface EffectiveProxy {
  proxy: Proxy;
  /** Which level the proxy was resolved from. */
  source: ProxyEntityType;
}

/**
 * Resolve the most specific proxy for an outbound-comms context.
 * Precedence: yacht override → interest override → client default.
 * Returns null when no proxy is set anywhere in the chain (caller falls back
 * to the client themselves).
 */
export async function resolveEffectiveProxy(args: {
  portId: string;
  clientId?: string | null;
  interestId?: string | null;
  yachtId?: string | null;
}): Promise<EffectiveProxy | null> {
  const { portId, clientId, interestId, yachtId } = args;
  if (yachtId) {
    const p = await getProxy(portId, 'yacht', yachtId);
    if (p) return { proxy: p, source: 'yacht' };
  }
  if (interestId) {
    const p = await getProxy(portId, 'interest', interestId);
    if (p) return { proxy: p, source: 'interest' };
  }
  if (clientId) {
    const p = await getProxy(portId, 'client', clientId);
    if (p) return { proxy: p, source: 'client' };
  }
  return null;
}
