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

import { db } from '@/lib/db';
import { clientNotes, clients } from '@/lib/db/schema/clients';
import { interestNotes, interests } from '@/lib/db/schema/interests';
import { yachtNotes, yachts } from '@/lib/db/schema/yachts';
import { companyNotes, companies } from '@/lib/db/schema/companies';
import { userProfiles } from '@/lib/db/schema/users';
import { NotFoundError, ValidationError } from '@/lib/errors';
import type { CreateNoteInput, UpdateNoteInput } from '@/lib/validators/notes';

const EDIT_WINDOW_MS = 15 * 60 * 1000; // 15 minutes

type EntityType = 'clients' | 'interests' | 'yachts' | 'companies';

// ─── Helpers ─────────────────────────────────────────────────────────────────

async function verifyParentBelongsToPort(
  entityType: EntityType,
  entityId: string,
  portId: string,
): Promise<void> {
  if (entityType === 'clients') {
    const r = await db
      .select({ id: clients.id })
      .from(clients)
      .where(and(eq(clients.id, entityId), eq(clients.portId, portId)))
      .limit(1);
    if (!r.length) throw new NotFoundError('Client');
  } else if (entityType === 'interests') {
    const r = await db
      .select({ id: interests.id })
      .from(interests)
      .where(and(eq(interests.id, entityId), eq(interests.portId, portId)))
      .limit(1);
    if (!r.length) throw new NotFoundError('Interest');
  } else if (entityType === 'yachts') {
    const r = await db
      .select({ id: yachts.id })
      .from(yachts)
      .where(and(eq(yachts.id, entityId), eq(yachts.portId, portId)))
      .limit(1);
    if (!r.length) throw new NotFoundError('Yacht');
  } else {
    const r = await db
      .select({ id: companies.id })
      .from(companies)
      .where(and(eq(companies.id, entityId), eq(companies.portId, portId)))
      .limit(1);
    if (!r.length) throw new NotFoundError('Company');
  }
}

// ─── Service ─────────────────────────────────────────────────────────────────

export async function listForEntity(portId: string, entityType: EntityType, entityId: string) {
  await verifyParentBelongsToPort(entityType, entityId, portId);

  if (entityType === 'clients') {
    return db
      .select({
        id: clientNotes.id,
        clientId: clientNotes.clientId,
        authorId: clientNotes.authorId,
        content: clientNotes.content,
        mentions: clientNotes.mentions,
        isLocked: clientNotes.isLocked,
        createdAt: clientNotes.createdAt,
        updatedAt: clientNotes.updatedAt,
        authorName: userProfiles.displayName,
      })
      .from(clientNotes)
      .leftJoin(userProfiles, eq(userProfiles.userId, clientNotes.authorId))
      .where(eq(clientNotes.clientId, entityId))
      .orderBy(desc(clientNotes.createdAt));
  } else if (entityType === 'interests') {
    return db
      .select({
        id: interestNotes.id,
        interestId: interestNotes.interestId,
        authorId: interestNotes.authorId,
        content: interestNotes.content,
        mentions: interestNotes.mentions,
        isLocked: interestNotes.isLocked,
        createdAt: interestNotes.createdAt,
        updatedAt: interestNotes.updatedAt,
        authorName: userProfiles.displayName,
      })
      .from(interestNotes)
      .leftJoin(userProfiles, eq(userProfiles.userId, interestNotes.authorId))
      .where(eq(interestNotes.interestId, entityId))
      .orderBy(desc(interestNotes.createdAt));
  } else if (entityType === 'yachts') {
    return db
      .select({
        id: yachtNotes.id,
        yachtId: yachtNotes.yachtId,
        authorId: yachtNotes.authorId,
        content: yachtNotes.content,
        mentions: yachtNotes.mentions,
        isLocked: yachtNotes.isLocked,
        createdAt: yachtNotes.createdAt,
        updatedAt: yachtNotes.updatedAt,
        authorName: userProfiles.displayName,
      })
      .from(yachtNotes)
      .leftJoin(userProfiles, eq(userProfiles.userId, yachtNotes.authorId))
      .where(eq(yachtNotes.yachtId, entityId))
      .orderBy(desc(yachtNotes.createdAt));
  } else {
    return db
      .select({
        id: companyNotes.id,
        companyId: companyNotes.companyId,
        authorId: companyNotes.authorId,
        content: companyNotes.content,
        mentions: companyNotes.mentions,
        isLocked: companyNotes.isLocked,
        createdAt: companyNotes.createdAt,
        updatedAt: companyNotes.createdAt,
        authorName: userProfiles.displayName,
      })
      .from(companyNotes)
      .leftJoin(userProfiles, eq(userProfiles.userId, companyNotes.authorId))
      .where(eq(companyNotes.companyId, entityId))
      .orderBy(desc(companyNotes.createdAt));
  }
}

export async function create(
  portId: string,
  entityType: EntityType,
  entityId: string,
  authorId: string,
  data: CreateNoteInput,
) {
  await verifyParentBelongsToPort(entityType, entityId, portId);

  if (entityType === 'yachts') {
    const [note] = await db
      .insert(yachtNotes)
      .values({ yachtId: entityId, authorId, content: data.content })
      .returning();
    if (!note) throw new Error('Insert failed');
    const profile = await db
      .select({ displayName: userProfiles.displayName })
      .from(userProfiles)
      .where(eq(userProfiles.userId, authorId))
      .limit(1);
    return { ...note, authorName: profile[0]?.displayName ?? null };
  }
  if (entityType === 'companies') {
    const [note] = await db
      .insert(companyNotes)
      .values({ companyId: entityId, authorId, content: data.content })
      .returning();
    if (!note) throw new Error('Insert failed');
    const profile = await db
      .select({ displayName: userProfiles.displayName })
      .from(userProfiles)
      .where(eq(userProfiles.userId, authorId))
      .limit(1);
    return { ...note, authorName: profile[0]?.displayName ?? null, updatedAt: note.createdAt };
  }
  if (entityType === 'clients') {
    const [note] = await db
      .insert(clientNotes)
      .values({ clientId: entityId, authorId, content: data.content })
      .returning();

    if (!note) throw new Error('Insert failed');

    const profile = await db
      .select({ displayName: userProfiles.displayName })
      .from(userProfiles)
      .where(eq(userProfiles.userId, authorId))
      .limit(1);

    const authorName = profile[0]?.displayName ?? null;

    // Fire mention notifications (fire-and-forget)
    if (note.mentions && note.mentions.length > 0) {
      for (const mentionedUserId of note.mentions) {
        void import('@/lib/services/notifications.service').then(({ createNotification }) =>
          createNotification({
            portId,
            userId: mentionedUserId,
            type: 'mention',
            title: 'You were mentioned in a note',
            description: `${authorName ?? 'Someone'} mentioned you in a note`,
            link: `/clients/${entityId}`,
            entityType: 'client',
            entityId,
            dedupeKey: `note:${note.id}:mention:${mentionedUserId}`,
          }),
        );
      }
    }

    return { ...note, authorName };
  } else {
    const [note] = await db
      .insert(interestNotes)
      .values({ interestId: entityId, authorId, content: data.content })
      .returning();

    if (!note) throw new Error('Insert failed');

    const profile = await db
      .select({ displayName: userProfiles.displayName })
      .from(userProfiles)
      .where(eq(userProfiles.userId, authorId))
      .limit(1);

    const authorName = profile[0]?.displayName ?? null;

    // Fire mention notifications (fire-and-forget)
    if (note.mentions && note.mentions.length > 0) {
      for (const mentionedUserId of note.mentions) {
        void import('@/lib/services/notifications.service').then(({ createNotification }) =>
          createNotification({
            portId,
            userId: mentionedUserId,
            type: 'mention',
            title: 'You were mentioned in a note',
            description: `${authorName ?? 'Someone'} mentioned you in a note`,
            link: `/interests/${entityId}`,
            entityType: 'interest',
            entityId,
            dedupeKey: `note:${note.id}:mention:${mentionedUserId}`,
          }),
        );
      }
    }

    return { ...note, authorName };
  }
  throw new Error(`Unsupported entityType: ${entityType as string}`);
}

export async function update(
  portId: string,
  entityType: EntityType,
  entityId: string,
  noteId: string,
  data: UpdateNoteInput,
) {
  await verifyParentBelongsToPort(entityType, entityId, portId);

  if (entityType === 'yachts') {
    const [existing] = await db
      .select()
      .from(yachtNotes)
      .where(and(eq(yachtNotes.id, noteId), eq(yachtNotes.yachtId, entityId)))
      .limit(1);
    if (!existing) throw new NotFoundError('Note');
    if (Date.now() - new Date(existing.createdAt).getTime() > EDIT_WINDOW_MS) {
      throw new ValidationError('Note edit window has expired (15 minutes)');
    }
    const [updated] = await db
      .update(yachtNotes)
      .set({ content: data.content, updatedAt: new Date() })
      .where(eq(yachtNotes.id, noteId))
      .returning();
    if (!updated) throw new NotFoundError('Note');
    const profile = await db
      .select({ displayName: userProfiles.displayName })
      .from(userProfiles)
      .where(eq(userProfiles.userId, updated.authorId))
      .limit(1);
    return { ...updated, authorName: profile[0]?.displayName ?? null };
  }
  if (entityType === 'companies') {
    const [existing] = await db
      .select()
      .from(companyNotes)
      .where(and(eq(companyNotes.id, noteId), eq(companyNotes.companyId, entityId)))
      .limit(1);
    if (!existing) throw new NotFoundError('Note');
    if (Date.now() - new Date(existing.createdAt).getTime() > EDIT_WINDOW_MS) {
      throw new ValidationError('Note edit window has expired (15 minutes)');
    }
    const [updated] = await db
      .update(companyNotes)
      .set({ content: data.content })
      .where(eq(companyNotes.id, noteId))
      .returning();
    if (!updated) throw new NotFoundError('Note');
    const profile = await db
      .select({ displayName: userProfiles.displayName })
      .from(userProfiles)
      .where(eq(userProfiles.userId, updated.authorId))
      .limit(1);
    return {
      ...updated,
      authorName: profile[0]?.displayName ?? null,
      updatedAt: updated.createdAt,
    };
  }
  if (entityType === 'clients') {
    const [existing] = await db
      .select()
      .from(clientNotes)
      .where(and(eq(clientNotes.id, noteId), eq(clientNotes.clientId, entityId)))
      .limit(1);

    if (!existing) throw new NotFoundError('Note');
    if (Date.now() - new Date(existing.createdAt).getTime() > EDIT_WINDOW_MS) {
      throw new ValidationError('Note edit window has expired (15 minutes)');
    }

    const [updated] = await db
      .update(clientNotes)
      .set({ content: data.content, updatedAt: new Date() })
      .where(eq(clientNotes.id, noteId))
      .returning();

    if (!updated) throw new NotFoundError('Note');

    const profile = await db
      .select({ displayName: userProfiles.displayName })
      .from(userProfiles)
      .where(eq(userProfiles.userId, updated.authorId))
      .limit(1);

    return { ...updated, authorName: profile[0]?.displayName ?? null };
  } else {
    const [existing] = await db
      .select()
      .from(interestNotes)
      .where(and(eq(interestNotes.id, noteId), eq(interestNotes.interestId, entityId)))
      .limit(1);

    if (!existing) throw new NotFoundError('Note');
    if (Date.now() - new Date(existing.createdAt).getTime() > EDIT_WINDOW_MS) {
      throw new ValidationError('Note edit window has expired (15 minutes)');
    }

    const [updated] = await db
      .update(interestNotes)
      .set({ content: data.content, updatedAt: new Date() })
      .where(eq(interestNotes.id, noteId))
      .returning();

    if (!updated) throw new NotFoundError('Note');

    const profile = await db
      .select({ displayName: userProfiles.displayName })
      .from(userProfiles)
      .where(eq(userProfiles.userId, updated.authorId))
      .limit(1);

    return { ...updated, authorName: profile[0]?.displayName ?? null };
  }
}

export async function deleteNote(
  portId: string,
  entityType: EntityType,
  entityId: string,
  noteId: string,
) {
  await verifyParentBelongsToPort(entityType, entityId, portId);

  if (entityType === 'yachts') {
    const [existing] = await db
      .select()
      .from(yachtNotes)
      .where(and(eq(yachtNotes.id, noteId), eq(yachtNotes.yachtId, entityId)))
      .limit(1);
    if (!existing) throw new NotFoundError('Note');
    if (Date.now() - new Date(existing.createdAt).getTime() > EDIT_WINDOW_MS) {
      throw new ValidationError('Note edit window has expired (15 minutes)');
    }
    await db.delete(yachtNotes).where(eq(yachtNotes.id, noteId));
    return existing;
  }
  if (entityType === 'companies') {
    const [existing] = await db
      .select()
      .from(companyNotes)
      .where(and(eq(companyNotes.id, noteId), eq(companyNotes.companyId, entityId)))
      .limit(1);
    if (!existing) throw new NotFoundError('Note');
    if (Date.now() - new Date(existing.createdAt).getTime() > EDIT_WINDOW_MS) {
      throw new ValidationError('Note edit window has expired (15 minutes)');
    }
    await db.delete(companyNotes).where(eq(companyNotes.id, noteId));
    return existing;
  }
  if (entityType === 'clients') {
    const [existing] = await db
      .select()
      .from(clientNotes)
      .where(and(eq(clientNotes.id, noteId), eq(clientNotes.clientId, entityId)))
      .limit(1);

    if (!existing) throw new NotFoundError('Note');
    if (Date.now() - new Date(existing.createdAt).getTime() > EDIT_WINDOW_MS) {
      throw new ValidationError('Note edit window has expired (15 minutes)');
    }

    await db.delete(clientNotes).where(eq(clientNotes.id, noteId));
    return existing;
  } else {
    const [existing] = await db
      .select()
      .from(interestNotes)
      .where(and(eq(interestNotes.id, noteId), eq(interestNotes.interestId, entityId)))
      .limit(1);

    if (!existing) throw new NotFoundError('Note');
    if (Date.now() - new Date(existing.createdAt).getTime() > EDIT_WINDOW_MS) {
      throw new ValidationError('Note edit window has expired (15 minutes)');
    }

    await db.delete(interestNotes).where(eq(interestNotes.id, noteId));
    return existing;
  }
}
