/**
 * Inquiries workbench service: list/filter/triage/get + convert-to-client and
 * convert-to-interest (find-or-create client, tracking columns, port isolation).
 */

import { describe, it, expect, beforeAll, vi } from 'vitest';
import { eq } from 'drizzle-orm';

vi.mock('@/lib/socket/server', () => ({
  emitToRoom: vi.fn(),
}));

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 { user } from '@/lib/db/schema/users';
import {
  listInquiries,
  getInquiryById,
  triageInquiry,
  convertInquiryToClient,
  convertInquiryToInterest,
} from '@/lib/services/inquiries.service';
import { makePort } from '../helpers/factories';
import type { AuditMeta } from '@/lib/audit';

let META: AuditMeta;

beforeAll(async () => {
  const [u] = await db.select({ id: user.id }).from(user).limit(1);
  if (!u) throw new Error('No user available; run pnpm db:seed first');
  META = { userId: u.id, portId: '', ipAddress: '127.0.0.1', userAgent: 'test' };
});

function metaFor(portId: string): AuditMeta {
  return { ...META, portId };
}

async function seedInquiry(
  portId: string,
  opts: {
    kind?: 'berth_inquiry' | 'residence_inquiry' | 'contact_form';
    triageState?: string;
    contactName?: string | null;
    contactEmail?: string | null;
    payload?: Record<string, unknown>;
  } = {},
) {
  const [row] = await db
    .insert(websiteSubmissions)
    .values({
      portId,
      submissionId: crypto.randomUUID(),
      kind: opts.kind ?? 'contact_form',
      payload: opts.payload ?? {},
      contactName: opts.contactName ?? 'Jane Doe',
      contactEmail: opts.contactEmail ?? 'jane@example.com',
      triageState: opts.triageState ?? 'open',
    })
    .returning();
  return row!;
}

describe('inquiries.service — list / get / triage', () => {
  it('filters by kind and state, searches name/email, scoped to port', async () => {
    const port = await makePort();
    const other = await makePort();
    await seedInquiry(port.id, {
      kind: 'contact_form',
      contactName: 'Alice Smith',
      contactEmail: 'alice@x.com',
    });
    await seedInquiry(port.id, {
      kind: 'berth_inquiry',
      contactName: 'Bob Jones',
      contactEmail: 'bob@x.com',
    });
    await seedInquiry(port.id, {
      kind: 'contact_form',
      triageState: 'dismissed',
      contactName: 'Carol',
    });
    await seedInquiry(other.id, {
      kind: 'contact_form',
      contactName: 'Alice Smith',
      contactEmail: 'alice@x.com',
    });

    const base = { page: 1, limit: 25, order: 'desc' as const, includeArchived: false };

    // inbox (open+assigned) excludes the dismissed one
    const inbox = await listInquiries(port.id, { ...base, state: 'inbox' });
    expect(inbox.total).toBe(2);

    // kind filter
    const contacts = await listInquiries(port.id, { ...base, state: 'all', kind: 'contact_form' });
    expect(contacts.total).toBe(2);

    // search by name
    const alice = await listInquiries(port.id, { ...base, state: 'all', search: 'alice' });
    expect(alice.total).toBe(1);
    expect(alice.data[0]!.contactName).toBe('Alice Smith');

    // port isolation
    expect(alice.data.every((r) => r.portId === port.id)).toBe(true);
  });

  it('triageInquiry updates state + triagedBy; getInquiryById returns the row', async () => {
    const port = await makePort();
    const row = await seedInquiry(port.id);
    const updated = await triageInquiry(row.id, port.id, 'assigned', metaFor(port.id));
    expect(updated.triageState).toBe('assigned');
    expect(updated.triagedBy).toBe(META.userId);

    const fetched = await getInquiryById(row.id, port.id);
    expect(fetched.id).toBe(row.id);
    expect(fetched.convertedClient).toBeNull();
  });
});

describe('inquiries.service — convert', () => {
  it('convert to client creates a new client when no email match', async () => {
    const port = await makePort();
    const row = await seedInquiry(port.id, {
      contactName: 'New Lead',
      contactEmail: 'newlead@example.com',
      payload: {
        first_name: 'New',
        last_name: 'Lead',
        email: 'newlead@example.com',
        phone: '+15551234567',
      },
    });

    const res = await convertInquiryToClient(row.id, port.id, metaFor(port.id));
    expect(res.clientId).toBeTruthy();
    expect(res.interestId).toBeNull();

    const [c] = await db.select().from(clients).where(eq(clients.id, res.clientId)).limit(1);
    expect(c!.fullName).toBe('New Lead');
    expect(c!.source).toBe('website');

    const sub = await getInquiryById(row.id, port.id);
    expect(sub.triageState).toBe('converted');
    expect(sub.convertedClientId).toBe(res.clientId);
  });

  it('convert links an existing client on a unique email match (no duplicate)', async () => {
    const port = await makePort();
    // Pre-existing client with the same email.
    const [existing] = await db
      .insert(clients)
      .values({ portId: port.id, fullName: 'Existing Client', source: 'manual' })
      .returning();
    await db.insert(clientContacts).values({
      clientId: existing!.id,
      channel: 'email',
      value: 'dup@example.com',
      isPrimary: true,
    });

    const row = await seedInquiry(port.id, {
      contactEmail: 'dup@example.com',
      payload: { email: 'dup@example.com' },
    });
    const res = await convertInquiryToClient(row.id, port.id, metaFor(port.id));
    expect(res.clientId).toBe(existing!.id);

    const all = await db.select().from(clients).where(eq(clients.portId, port.id));
    expect(all).toHaveLength(1); // no duplicate created
  });

  it('convert to interest find-or-creates the client and creates an interest', async () => {
    const port = await makePort();
    const row = await seedInquiry(port.id, {
      contactName: 'Deal Maker',
      contactEmail: 'deal@example.com',
      payload: { first_name: 'Deal', last_name: 'Maker', email: 'deal@example.com' },
    });

    const res = await convertInquiryToInterest(row.id, port.id, metaFor(port.id));
    expect(res.clientId).toBeTruthy();
    expect(res.interestId).toBeTruthy();

    const [i] = await db.select().from(interests).where(eq(interests.id, res.interestId!)).limit(1);
    expect(i!.clientId).toBe(res.clientId);
    expect(i!.pipelineStage).toBe('enquiry');

    const sub = await getInquiryById(row.id, port.id);
    expect(sub.triageState).toBe('converted');
    expect(sub.convertedInterestId).toBe(res.interestId);
    expect(sub.convertedClientId).toBe(res.clientId);
  });

  it('convert to interest twice is rejected', async () => {
    const port = await makePort();
    const row = await seedInquiry(port.id, {
      contactEmail: 'once@example.com',
      payload: { email: 'once@example.com' },
    });
    await convertInquiryToInterest(row.id, port.id, metaFor(port.id));
    await expect(convertInquiryToInterest(row.id, port.id, metaFor(port.id))).rejects.toThrow();
  });
});
