import { describe, expect, it, mock } from 'bun:test';
import { QueryClient } from '@tanstack/react-query';
import { createResearchActions } from './researchActions';
import { researchApi } from './researchApi';
import { researchQueryKeys } from './researchQueries';
import { useResearchUiStore } from './researchUiStore';
import type {
  NoteCaptureResult,
  PipelineJob,
  ResearchNote,
  ResearchWorkspace,
  WorkspaceProjection,
} from '../types/research';

const workspace: ResearchWorkspace = {
  id: 'workspace-1',
  name: 'NVDA Research',
  primaryTicker: 'NVDA',
  scope: 'single_company',
  stage: 'capture',
  defaultView: 'canvas',
  pinnedNoteIds: [],
  archived: false,
  createdAt: '2026-04-09T10:00:00Z',
  updatedAt: '2026-04-09T10:00:00Z',
};

const note = (overrides: Partial<ResearchNote> = {}): ResearchNote => ({
  id: 'note-1',
  workspaceId: workspace.id,
  rawText: 'Demand is improving.',
  cleanedText: 'Demand is improving.',
  embeds: [],
  fileIds: [],
  title: 'Demand',
  noteType: 'claim',
  analystStatus: 'captured',
  confidence: 0.7,
  evidenceStatus: 'source_linked',
  inferredLinks: [],
  ghostStatus: 'none',
  thesisStatus: 'none',
  createdAt: '2026-04-09T10:00:00Z',
  updatedAt: '2026-04-09T10:00:00Z',
  provenance: {
    createdBy: 'manual',
    captureMethod: 'quick_entry',
    sourceKind: 'manual',
    createdAt: '2026-04-09T10:00:00Z',
    rawInputHash: 'hash',
  },
  tags: [],
  catalysts: [],
  risks: [],
  valuationRefs: [],
  priority: 'normal',
  pinned: false,
  archived: false,
  revision: 1,
  ...overrides,
});

const projection = (): WorkspaceProjection => ({
  workspace,
  activeView: 'canvas',
  notes: [note()],
  links: [],
  ghosts: [],
  memoBlocks: [],
  graphNodes: [],
  graphEdges: [],
  kanbanColumns: [],
  timelineEvents: [],
});

const resetStore = () => {
  useResearchUiStore.setState({
    viewByWorkspaceId: {},
    filtersByWorkspaceId: {},
    memoDraftByWorkspaceId: {},
    density: 'comfortable',
    sidebarCollapsed: false,
    inspectorPinned: true,
    memoPreview: true,
    activeWorkspaceId: null,
    selectionByWorkspaceId: {},
    captureModal: {
      isOpen: false,
      draft: {
        rawText: '',
        bodyHtml: '',
        embeds: [],
        fileIds: [],
        ticker: '',
        workspaceId: '',
        sourceUrl: '',
        sourceTitle: '',
        sourceKind: 'manual',
        userNoteTypeOverride: undefined,
        includeCurrentContext: true,
        captureMode: 'standard',
        linkUrlDraft: '',
      },
      seedToken: null,
      contextLabel: undefined,
    },
    createWorkspaceModal: {
      isOpen: false,
      tickerDraft: '',
    },
    mobilePane: 'none',
    lastNavigationIntentToken: null,
    mutationStateByKey: {},
  });
};

describe('researchActions', () => {
  it('patches the snapshot and invalidates dependent queries after note updates', async () => {
    resetStore();
    const queryClient = new QueryClient();
    queryClient.setQueryData(researchQueryKeys.workspaces, [workspace]);
    queryClient.setQueryData(researchQueryKeys.snapshot(workspace.id), projection());

    const invalidateQueries = mock(async () => undefined);
    queryClient.invalidateQueries = invalidateQueries as typeof queryClient.invalidateQueries;

    const originalUpdateNote = researchApi.updateNote;
    researchApi.updateNote = mock(
      async () =>
        note({
          id: 'note-1',
          title: 'Updated title',
          pinned: true,
        }),
    ) as typeof researchApi.updateNote;

    const actions = createResearchActions(queryClient);
    await actions.updateNote({
      noteId: 'note-1',
      workspaceId: workspace.id,
      title: 'Updated title',
      pinned: true,
    });

    const updatedSnapshot = queryClient.getQueryData<WorkspaceProjection>(
      researchQueryKeys.snapshot(workspace.id),
    );
    expect(updatedSnapshot?.notes[0].title).toBe('Updated title');
    expect(updatedSnapshot?.notes[0].pinned).toBe(true);
    expect(invalidateQueries).toHaveBeenCalledWith({
      queryKey: researchQueryKeys.searchIdsPrefix(workspace.id),
    });
    expect(invalidateQueries).toHaveBeenCalledWith({
      queryKey: researchQueryKeys.auditTrail('note-1'),
    });
    expect(invalidateQueries).toHaveBeenCalledWith({
      queryKey: researchQueryKeys.jobs(workspace.id),
    });

    researchApi.updateNote = originalUpdateNote;
  });

  it('patches snapshot and queued jobs immediately after capturing a note', async () => {
    resetStore();
    const queryClient = new QueryClient();
    queryClient.setQueryData(researchQueryKeys.workspaces, [workspace]);
    queryClient.setQueryData(researchQueryKeys.snapshot(workspace.id), projection());

    const queuedJob: PipelineJob = {
      id: 'job-1',
      workspaceId: workspace.id,
      entityId: 'note-2',
      jobKind: 'enrich_note',
      status: 'queued',
      attemptCount: 0,
      maxAttempts: 3,
      payloadJson: {},
      createdAt: '2026-04-09T10:00:00Z',
      updatedAt: '2026-04-09T10:00:00Z',
    };

    const originalCaptureNote = researchApi.captureNote;
    researchApi.captureNote = mock(
      async (): Promise<NoteCaptureResult> => ({
        note: note({
          id: 'note-2',
          title: 'Captured note',
        }),
        placementHint: {
          tileLane: 'active',
          kanbanColumn: 'claim',
        },
        queuedJobs: [queuedJob],
      }),
    ) as typeof researchApi.captureNote;

    const actions = createResearchActions(queryClient);
    await actions.captureNote({
      draft: {
        rawText: 'Captured note',
        bodyHtml: '<p>Captured note</p>',
        embeds: [
          {
            url: 'https://example.com/article',
            title: 'Example article',
            publisher: 'example.com',
            excerpt: 'Example excerpt',
            kind: 'article',
          },
        ],
        fileIds: ['file-1'],
        ticker: 'NVDA',
        workspaceId: workspace.id,
        sourceUrl: '',
        sourceTitle: '',
        sourceKind: 'manual',
        userNoteTypeOverride: undefined,
        includeCurrentContext: true,
        captureMode: 'standard',
        linkUrlDraft: '',
      },
      explicitWorkspaceId: workspace.id,
      autoCreateFromTicker: true,
    });

    const updatedSnapshot = queryClient.getQueryData<WorkspaceProjection>(
      researchQueryKeys.snapshot(workspace.id),
    );
    const jobs = queryClient.getQueryData<PipelineJob[]>(
      researchQueryKeys.jobs(workspace.id),
    );

    expect(updatedSnapshot?.notes.some((current) => current.id === 'note-2')).toBe(true);
    expect(jobs?.[0]?.id).toBe('job-1');
    expect(useResearchUiStore.getState().activeWorkspaceId).toBe(workspace.id);
    expect(useResearchUiStore.getState().captureModal.isOpen).toBe(false);
    const captureRequest = (
      researchApi.captureNote as unknown as { mock: { calls: Array<[Parameters<typeof researchApi.captureNote>[0]]> } }
    ).mock.calls[0]?.[0];
    expect(captureRequest?.bodyHtml).toBe('<p>Captured note</p>');
    expect(captureRequest?.embeds?.[0]?.url).toBe('https://example.com/article');
    expect(captureRequest?.embeds?.[0]?.title).toBe('Example article');
    expect(captureRequest?.fileIds).toEqual(['file-1']);

    researchApi.captureNote = originalCaptureNote;
  });
});
