/**
 * Security regression: AI email-draft jobs are bound to the requesting
 * user + port. A foreign caller who knows the jobId must NOT receive the
 * drafted subject/body.
 */

import { describe, it, expect, vi, beforeEach } from 'vitest';

import { ForbiddenError, ValidationError } from '@/lib/errors';

// Mock the queue. Each test sets up a fresh per-test job map.
const fakeJobs = new Map<string, { data: unknown; returnvalue: unknown; state: string }>();

vi.mock('@/lib/queue', () => ({
  getQueue: () => ({
    add: vi.fn(async (_name: string, data: unknown, opts: { jobId: string }) => {
      fakeJobs.set(opts.jobId, { data, returnvalue: null, state: 'completed' });
      return { id: opts.jobId };
    }),
    getJob: vi.fn(async (id: string) => {
      const j = fakeJobs.get(id);
      if (!j) return null;
      return {
        data: j.data,
        returnvalue: j.returnvalue,
        getState: async () => j.state,
      };
    }),
  }),
}));

// Mock interest/client lookups so requestEmailDraft doesn't hit the DB.
vi.mock('@/lib/db', () => ({
  db: {
    query: {
      interests: {
        findFirst: vi.fn(async ({ where: _w }) => ({ id: 'iA', portId: 'pA' })),
      },
      clients: {
        findFirst: vi.fn(async ({ where: _w }) => ({ id: 'cA', portId: 'pA' })),
      },
    },
  },
}));

beforeEach(() => {
  fakeJobs.clear();
  vi.clearAllMocks();
});

describe('email-draft job binding', () => {
  it('rejects readers with a different userId', async () => {
    const { requestEmailDraft, getEmailDraftResult } =
      await import('@/lib/services/email-draft.service');

    const { jobId } = await requestEmailDraft('user-A', {
      interestId: 'iA',
      clientId: 'cA',
      portId: 'pA',
      context: 'follow_up',
    });

    // Wire in a completed return value so a successful path would otherwise
    // produce a result.
    fakeJobs.get(jobId)!.returnvalue = {
      subject: 'leak',
      body: 'leak',
      generatedAt: new Date().toISOString(),
    };

    await expect(getEmailDraftResult(jobId, { userId: 'user-B', portId: 'pA' })).rejects.toThrow(
      ForbiddenError,
    );
  });

  it('rejects readers with a different portId', async () => {
    const { requestEmailDraft, getEmailDraftResult } =
      await import('@/lib/services/email-draft.service');

    const { jobId } = await requestEmailDraft('user-A', {
      interestId: 'iA',
      clientId: 'cA',
      portId: 'pA',
      context: 'follow_up',
    });
    fakeJobs.get(jobId)!.returnvalue = {
      subject: 'leak',
      body: 'leak',
      generatedAt: new Date().toISOString(),
    };

    await expect(getEmailDraftResult(jobId, { userId: 'user-A', portId: 'pB' })).rejects.toThrow(
      ForbiddenError,
    );
  });

  it('returns drafted content to the original requester', async () => {
    const { requestEmailDraft, getEmailDraftResult } =
      await import('@/lib/services/email-draft.service');

    const { jobId } = await requestEmailDraft('user-A', {
      interestId: 'iA',
      clientId: 'cA',
      portId: 'pA',
      context: 'follow_up',
    });
    fakeJobs.get(jobId)!.returnvalue = {
      subject: 'subject-A',
      body: 'body-A',
      generatedAt: new Date().toISOString(),
    };

    const result = await getEmailDraftResult(jobId, { userId: 'user-A', portId: 'pA' });
    expect(result?.subject).toBe('subject-A');
    expect(result?.body).toBe('body-A');
  });

  it('jobId is a UUID, not a sequential integer', async () => {
    const { requestEmailDraft } = await import('@/lib/services/email-draft.service');

    const { jobId } = await requestEmailDraft('user-A', {
      interestId: 'iA',
      clientId: 'cA',
      portId: 'pA',
      context: 'follow_up',
    });
    // Crude UUID-shape check: 8-4-4-4-12 hex.
    expect(jobId).toMatch(/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i);
  });

  it('rejects requests whose interest is not in the supplied port', async () => {
    const { db } = await import('@/lib/db');
    (db.query.interests.findFirst as ReturnType<typeof vi.fn>).mockResolvedValueOnce(null);

    const { requestEmailDraft } = await import('@/lib/services/email-draft.service');

    await expect(
      requestEmailDraft('user-A', {
        interestId: 'foreign-interest',
        clientId: 'cA',
        portId: 'pA',
        context: 'follow_up',
      }),
    ).rejects.toThrow(ValidationError);
  });
});
