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

import { db } from '@/lib/db';
import { residentialClients, residentialInterests } from '@/lib/db/schema/residential';
import { createAuditLog, type AuditMeta } from '@/lib/audit';
import { CodedError, NotFoundError } from '@/lib/errors';
import { emitToRoom } from '@/lib/socket/server';
import { buildListQuery } from '@/lib/db/query-builder';
import { diffEntity } from '@/lib/entity-diff';
import { softDelete, restore } from '@/lib/db/utils';
import type {
  CreateResidentialClientInput,
  CreateResidentialInterestInput,
  ListResidentialClientsInput,
  ListResidentialInterestsInput,
  UpdateResidentialClientInput,
  UpdateResidentialInterestInput,
} from '@/lib/validators/residential';

// ─── Residential clients ─────────────────────────────────────────────────────

export async function listResidentialClients(portId: string, query: ListResidentialClientsInput) {
  const { page, limit, sort, order, search, includeArchived, status, source } = query;

  const filters = [];
  if (status) filters.push(eq(residentialClients.status, status));
  if (source) filters.push(eq(residentialClients.source, source));

  return buildListQuery({
    table: residentialClients,
    portIdColumn: residentialClients.portId,
    portId,
    idColumn: residentialClients.id,
    updatedAtColumn: residentialClients.updatedAt,
    filters,
    sort: sort
      ? {
          column:
            (residentialClients[sort as keyof typeof residentialClients] as never) ??
            residentialClients.updatedAt,
          direction: order ?? 'desc',
        }
      : undefined,
    page,
    pageSize: limit,
    searchColumns: [
      residentialClients.fullName,
      residentialClients.email,
      residentialClients.phone,
      residentialClients.placeOfResidence,
    ],
    searchTerm: search,
    includeArchived,
    archivedAtColumn: residentialClients.archivedAt,
  });
}

export async function getResidentialClientById(id: string, portId: string) {
  const client = await db.query.residentialClients.findFirst({
    where: and(eq(residentialClients.id, id), eq(residentialClients.portId, portId)),
  });
  if (!client) throw new NotFoundError('Residential client');

  const interests = await db.query.residentialInterests.findMany({
    where: eq(residentialInterests.residentialClientId, id),
    orderBy: (t, { desc }) => [desc(t.updatedAt)],
  });

  return { ...client, interests };
}

export async function createResidentialClient(
  portId: string,
  data: CreateResidentialClientInput,
  meta: AuditMeta,
) {
  const [row] = await db
    .insert(residentialClients)
    .values({ portId, ...data })
    .returning();
  if (!row)
    throw new CodedError('INSERT_RETURNING_EMPTY', {
      internalMessage: 'Failed to create residential client',
    });

  void createAuditLog({
    userId: meta.userId,
    portId,
    action: 'create',
    entityType: 'residential_client',
    entityId: row.id,
    newValue: { fullName: row.fullName, email: row.email ?? undefined },
    ipAddress: meta.ipAddress,
    userAgent: meta.userAgent,
  });
  emitToRoom(`port:${portId}`, 'residential_client:created', { id: row.id });

  return row;
}

export async function updateResidentialClient(
  id: string,
  portId: string,
  data: UpdateResidentialClientInput,
  meta: AuditMeta,
) {
  const before = await db.query.residentialClients.findFirst({
    where: and(eq(residentialClients.id, id), eq(residentialClients.portId, portId)),
  });
  if (!before) throw new NotFoundError('Residential client');

  const [updated] = await db
    .update(residentialClients)
    .set({ ...data, updatedAt: new Date() })
    .where(and(eq(residentialClients.id, id), eq(residentialClients.portId, portId)))
    .returning();
  if (!updated) throw new NotFoundError('Residential client');

  void createAuditLog({
    userId: meta.userId,
    portId,
    action: 'update',
    entityType: 'residential_client',
    entityId: id,
    oldValue: diffEntity(before, updated) as Record<string, unknown>,
    newValue: data as Record<string, unknown>,
    ipAddress: meta.ipAddress,
    userAgent: meta.userAgent,
  });
  emitToRoom(`port:${portId}`, 'residential_client:updated', { id });

  return updated;
}

export async function archiveResidentialClient(id: string, portId: string, meta: AuditMeta) {
  const existing = await db.query.residentialClients.findFirst({
    where: and(eq(residentialClients.id, id), eq(residentialClients.portId, portId)),
  });
  if (!existing) throw new NotFoundError('Residential client');

  await softDelete(residentialClients, residentialClients.id, id);

  void createAuditLog({
    userId: meta.userId,
    portId,
    action: 'archive',
    entityType: 'residential_client',
    entityId: id,
    ipAddress: meta.ipAddress,
    userAgent: meta.userAgent,
  });
  emitToRoom(`port:${portId}`, 'residential_client:archived', { id });
}

export async function restoreResidentialClient(id: string, portId: string, meta: AuditMeta) {
  const existing = await db.query.residentialClients.findFirst({
    where: and(eq(residentialClients.id, id), eq(residentialClients.portId, portId)),
  });
  if (!existing) throw new NotFoundError('Residential client');

  await restore(residentialClients, residentialClients.id, id);

  void createAuditLog({
    userId: meta.userId,
    portId,
    action: 'restore',
    entityType: 'residential_client',
    entityId: id,
    ipAddress: meta.ipAddress,
    userAgent: meta.userAgent,
  });
  emitToRoom(`port:${portId}`, 'residential_client:restored', { id });
}

// ─── Residential interests ───────────────────────────────────────────────────

export async function listResidentialInterests(
  portId: string,
  query: ListResidentialInterestsInput,
) {
  const {
    page,
    limit,
    sort,
    order,
    search,
    includeArchived,
    pipelineStage,
    assignedTo,
    residentialClientId,
  } = query;

  const filters = [];
  if (pipelineStage) filters.push(eq(residentialInterests.pipelineStage, pipelineStage));
  if (assignedTo) filters.push(eq(residentialInterests.assignedTo, assignedTo));
  if (residentialClientId)
    filters.push(eq(residentialInterests.residentialClientId, residentialClientId));

  return buildListQuery({
    table: residentialInterests,
    portIdColumn: residentialInterests.portId,
    portId,
    idColumn: residentialInterests.id,
    updatedAtColumn: residentialInterests.updatedAt,
    filters,
    sort: sort
      ? {
          column:
            (residentialInterests[sort as keyof typeof residentialInterests] as never) ??
            residentialInterests.updatedAt,
          direction: order ?? 'desc',
        }
      : undefined,
    page,
    pageSize: limit,
    searchColumns: [residentialInterests.notes, residentialInterests.preferences],
    searchTerm: search,
    includeArchived,
    archivedAtColumn: residentialInterests.archivedAt,
  });
}

export async function getResidentialInterestById(id: string, portId: string) {
  const interest = await db.query.residentialInterests.findFirst({
    where: and(eq(residentialInterests.id, id), eq(residentialInterests.portId, portId)),
  });
  if (!interest) throw new NotFoundError('Residential interest');

  // The residentialInterest is already port-scoped; pin the client read
  // to the same port too so a future drift (a foreign-port residential
  // client id ever landing on the interest) cannot leak.
  const client = await db.query.residentialClients.findFirst({
    where: and(
      eq(residentialClients.id, interest.residentialClientId),
      eq(residentialClients.portId, portId),
    ),
  });

  return { ...interest, client };
}

export async function createResidentialInterest(
  portId: string,
  data: CreateResidentialInterestInput,
  meta: AuditMeta,
) {
  // Validate the residential client belongs to this port - prevents
  // cross-port linking.
  const client = await db.query.residentialClients.findFirst({
    where: and(
      eq(residentialClients.id, data.residentialClientId),
      eq(residentialClients.portId, portId),
    ),
  });
  if (!client) throw new NotFoundError('Residential client');

  const [row] = await db
    .insert(residentialInterests)
    .values({ portId, ...data })
    .returning();
  if (!row)
    throw new CodedError('INSERT_RETURNING_EMPTY', {
      internalMessage: 'Failed to create residential interest',
    });

  void createAuditLog({
    userId: meta.userId,
    portId,
    action: 'create',
    entityType: 'residential_interest',
    entityId: row.id,
    newValue: { residentialClientId: row.residentialClientId, pipelineStage: row.pipelineStage },
    ipAddress: meta.ipAddress,
    userAgent: meta.userAgent,
  });
  emitToRoom(`port:${portId}`, 'residential_interest:created', { id: row.id });

  return row;
}

export async function updateResidentialInterest(
  id: string,
  portId: string,
  data: UpdateResidentialInterestInput,
  meta: AuditMeta,
) {
  const before = await db.query.residentialInterests.findFirst({
    where: and(eq(residentialInterests.id, id), eq(residentialInterests.portId, portId)),
  });
  if (!before) throw new NotFoundError('Residential interest');

  const [updated] = await db
    .update(residentialInterests)
    .set({ ...data, updatedAt: new Date() })
    .where(and(eq(residentialInterests.id, id), eq(residentialInterests.portId, portId)))
    .returning();
  if (!updated) throw new NotFoundError('Residential interest');

  void createAuditLog({
    userId: meta.userId,
    portId,
    action: 'update',
    entityType: 'residential_interest',
    entityId: id,
    oldValue: diffEntity(before, updated) as Record<string, unknown>,
    newValue: data as Record<string, unknown>,
    ipAddress: meta.ipAddress,
    userAgent: meta.userAgent,
  });
  emitToRoom(`port:${portId}`, 'residential_interest:updated', { id });

  return updated;
}

export async function archiveResidentialInterest(id: string, portId: string, meta: AuditMeta) {
  const existing = await db.query.residentialInterests.findFirst({
    where: and(eq(residentialInterests.id, id), eq(residentialInterests.portId, portId)),
  });
  if (!existing) throw new NotFoundError('Residential interest');

  await softDelete(residentialInterests, residentialInterests.id, id);

  void createAuditLog({
    userId: meta.userId,
    portId,
    action: 'archive',
    entityType: 'residential_interest',
    entityId: id,
    ipAddress: meta.ipAddress,
    userAgent: meta.userAgent,
  });
  emitToRoom(`port:${portId}`, 'residential_interest:archived', { id });
}
