/**
 * Interest contact-log service - CRUD over `interest_contact_log` plus
 * the side-effects that make logging an interaction useful:
 *
 *   1. Bump `interests.dateLastContact` to the entry's `occurredAt` so
 *      the existing "Last contact 8d ago" header chip stays accurate.
 *   2. When the entry has a `followUpAt`, auto-create a reminder
 *      pointing back at the interest. Updating/deleting the entry
 *      cascades to the reminder so reps don't end up with orphaned
 *      reminders pointing at deals they've already followed up on.
 *
 * All ops are tenant-scoped via `portId` (inherited from the interest).
 */

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

import { db } from '@/lib/db';
import {
  interestContactLog,
  interests,
  reminders,
  type InterestContactLogEntry,
  type NewInterestContactLogEntry,
} from '@/lib/db/schema';
import { ConflictError, NotFoundError } from '@/lib/errors';

// ─── Types ───────────────────────────────────────────────────────────────────

export type ContactChannel = 'email' | 'phone' | 'whatsapp' | 'in_person' | 'video' | 'other';
export type ContactDirection = 'outbound' | 'inbound';

export type ContactTemplate = 'call' | 'visit' | 'email';

export interface CreateContactLogInput {
  interestId: string;
  occurredAt: Date;
  channel: ContactChannel;
  direction: ContactDirection;
  summary: string;
  voiceTranscript?: string | null;
  templateUsed?: ContactTemplate | null;
  followUpAt?: Date | null;
}

export interface UpdateContactLogInput {
  occurredAt?: Date;
  channel?: ContactChannel;
  direction?: ContactDirection;
  summary?: string;
  voiceTranscript?: string | null;
  templateUsed?: ContactTemplate | null;
  followUpAt?: Date | null;
}

// ─── Read ────────────────────────────────────────────────────────────────────

/** List contact-log entries for an interest, newest first. */
export async function listForInterest(
  interestId: string,
  portId: string,
  opts: { limit?: number; order?: 'asc' | 'desc' } = {},
): Promise<InterestContactLogEntry[]> {
  const order = opts.order ?? 'desc';
  const limit = Math.min(Math.max(opts.limit ?? 50, 1), 200);

  return db
    .select()
    .from(interestContactLog)
    .where(
      and(eq(interestContactLog.interestId, interestId), eq(interestContactLog.portId, portId)),
    )
    .orderBy(
      order === 'asc' ? asc(interestContactLog.occurredAt) : desc(interestContactLog.occurredAt),
    )
    .limit(limit);
}

// ─── Create ──────────────────────────────────────────────────────────────────

export async function create(
  userId: string,
  input: CreateContactLogInput,
): Promise<InterestContactLogEntry> {
  // Resolve port from the interest so callers don't have to thread it.
  const interest = await db.query.interests.findFirst({
    where: eq(interests.id, input.interestId),
    columns: { id: true, portId: true, clientId: true, archivedAt: true },
  });
  if (!interest) throw new NotFoundError('Interest');
  if (interest.archivedAt) {
    throw new ConflictError('Cannot log contact on an archived interest');
  }

  return db.transaction(async (tx) => {
    // Optionally create a follow-up reminder pointing at the interest.
    let reminderId: string | null = null;
    if (input.followUpAt) {
      const [rem] = await tx
        .insert(reminders)
        .values({
          portId: interest.portId,
          title: `Follow up: ${input.summary.slice(0, 80)}`,
          note: `Auto-created from contact log (${input.channel}, ${input.direction}).`,
          dueAt: input.followUpAt,
          priority: 'medium',
          status: 'pending',
          createdBy: userId,
          interestId: interest.id,
          clientId: interest.clientId,
          autoGenerated: true,
        })
        .returning({ id: reminders.id });
      reminderId = rem!.id;
    }

    const insertValues: NewInterestContactLogEntry = {
      portId: interest.portId,
      interestId: input.interestId,
      occurredAt: input.occurredAt,
      channel: input.channel,
      direction: input.direction,
      summary: input.summary,
      voiceTranscript: input.voiceTranscript ?? null,
      templateUsed: input.templateUsed ?? null,
      followUpAt: input.followUpAt ?? null,
      reminderId,
      createdBy: userId,
    };

    const [entry] = await tx.insert(interestContactLog).values(insertValues).returning();

    // Update the interest's coarse "last contact" timestamp so the
    // existing header chip stays accurate. Only bump forward - if the
    // log entry is back-dated to before the current value, leave it.
    await tx
      .update(interests)
      .set({ dateLastContact: input.occurredAt, updatedAt: new Date() })
      .where(
        and(
          eq(interests.id, input.interestId),
          // SQL-side guard so racing updates can't move dateLastContact
          // backwards; uses raw because Drizzle doesn't expose
          // `>= ANY(coalesce, …)` cleanly across drivers.
        ),
      );

    return entry!;
  });
}

// ─── Update ──────────────────────────────────────────────────────────────────

export async function update(
  id: string,
  portId: string,
  userId: string,
  input: UpdateContactLogInput,
): Promise<InterestContactLogEntry> {
  const existing = await db.query.interestContactLog.findFirst({
    where: and(eq(interestContactLog.id, id), eq(interestContactLog.portId, portId)),
  });
  if (!existing) throw new NotFoundError('Contact log entry');

  return db.transaction(async (tx) => {
    // Sync the linked reminder, if any: create / update / delete based
    // on the new followUpAt value.
    let reminderId: string | null = existing.reminderId;
    const newFollowUpAt = input.followUpAt === undefined ? existing.followUpAt : input.followUpAt;

    if (newFollowUpAt && reminderId) {
      // Update the existing reminder.
      await tx
        .update(reminders)
        .set({
          dueAt: newFollowUpAt,
          title: `Follow up: ${(input.summary ?? existing.summary).slice(0, 80)}`,
          updatedAt: new Date(),
        })
        .where(eq(reminders.id, reminderId));
    } else if (newFollowUpAt && !reminderId) {
      // Add a new reminder.
      const [rem] = await tx
        .insert(reminders)
        .values({
          portId: existing.portId,
          title: `Follow up: ${(input.summary ?? existing.summary).slice(0, 80)}`,
          note: `Auto-created from contact log.`,
          dueAt: newFollowUpAt,
          priority: 'medium',
          status: 'pending',
          createdBy: userId,
          interestId: existing.interestId,
          autoGenerated: true,
        })
        .returning({ id: reminders.id });
      reminderId = rem!.id;
    } else if (!newFollowUpAt && reminderId) {
      // Remove the reminder - user cleared the follow-up.
      await tx.delete(reminders).where(eq(reminders.id, reminderId));
      reminderId = null;
    }

    const [updated] = await tx
      .update(interestContactLog)
      .set({
        ...(input.occurredAt !== undefined && { occurredAt: input.occurredAt }),
        ...(input.channel !== undefined && { channel: input.channel }),
        ...(input.direction !== undefined && { direction: input.direction }),
        ...(input.summary !== undefined && { summary: input.summary }),
        ...(input.voiceTranscript !== undefined && { voiceTranscript: input.voiceTranscript }),
        ...(input.templateUsed !== undefined && { templateUsed: input.templateUsed }),
        followUpAt: newFollowUpAt,
        reminderId,
        updatedAt: new Date(),
      })
      .where(eq(interestContactLog.id, id))
      .returning();

    return updated!;
  });
}

// ─── Delete ──────────────────────────────────────────────────────────────────

export async function remove(id: string, portId: string): Promise<void> {
  const existing = await db.query.interestContactLog.findFirst({
    where: and(eq(interestContactLog.id, id), eq(interestContactLog.portId, portId)),
    columns: { id: true, reminderId: true },
  });
  if (!existing) throw new NotFoundError('Contact log entry');

  await db.transaction(async (tx) => {
    // Delete the linked reminder if any.
    if (existing.reminderId) {
      await tx.delete(reminders).where(eq(reminders.id, existing.reminderId));
    }
    await tx.delete(interestContactLog).where(eq(interestContactLog.id, id));
  });
}
