/**
 * @vitest-environment node
 */
import { createEnvMock, workflowAuthzMockFns } from '@sim/testing'
import { beforeEach, describe, expect, it, vi } from 'vitest'

const {
  ensureWorkflowAccessMock,
  setWorkflowVariablesMock,
  recordAuditMock,
  executeWorkflowMock,
  getExecutionStateForWorkflowMock,
  getLatestExecutionStateWithExecutionIdMock,
} = vi.hoisted(() => ({
  ensureWorkflowAccessMock: vi.fn(),
  setWorkflowVariablesMock: vi.fn(),
  recordAuditMock: vi.fn(),
  executeWorkflowMock: vi.fn(),
  getExecutionStateForWorkflowMock: vi.fn(),
  getLatestExecutionStateWithExecutionIdMock: vi.fn(),
}))

vi.mock('@sim/audit', () => ({
  AuditAction: { WORKFLOW_VARIABLES_UPDATED: 'WORKFLOW_VARIABLES_UPDATED' },
  AuditResourceType: { WORKFLOW: 'WORKFLOW' },
  recordAudit: recordAuditMock,
}))

vi.mock('@sim/db', () => ({
  db: {},
  workflow: {},
}))

vi.mock('@/lib/api-key/orchestration', () => ({
  performCreateWorkspaceApiKey: vi.fn(),
}))

vi.mock('@/lib/core/config/env', () => createEnvMock({ INTERNAL_API_SECRET: 'secret' }))

vi.mock('@/lib/core/utils/request', () => ({
  generateRequestId: () => 'request-1',
}))

vi.mock('@/lib/core/utils/urls', () => ({
  getSocketServerUrl: () => 'http://socket.test',
}))

vi.mock('@/lib/workflows/executor/execute-workflow', () => ({
  executeWorkflow: executeWorkflowMock,
}))

vi.mock('@/lib/workflows/executor/execution-state', () => ({
  getExecutionStateForWorkflow: getExecutionStateForWorkflowMock,
  getLatestExecutionStateWithExecutionId: getLatestExecutionStateWithExecutionIdMock,
}))

vi.mock('@/lib/workflows/orchestration', () => ({
  performCreateFolder: vi.fn(),
  performCreateWorkflow: vi.fn(),
  performDeleteFolder: vi.fn(),
  performDeleteWorkflow: vi.fn(),
  performUpdateFolder: vi.fn(),
  performUpdateWorkflow: vi.fn(),
}))

vi.mock('@/lib/workflows/persistence/utils', () => ({
  loadWorkflowFromNormalizedTables: vi.fn(),
  saveWorkflowToNormalizedTables: vi.fn(),
}))

vi.mock('@/lib/workflows/sanitization/json-sanitizer', () => ({
  sanitizeForCopilot: vi.fn((state) => state),
}))

vi.mock('@/lib/workflows/utils', () => ({
  listFolders: vi.fn(),
  setWorkflowVariables: setWorkflowVariablesMock,
  verifyFolderWorkspace: vi.fn(),
}))

vi.mock('@/executor/utils/errors', () => ({
  hasExecutionResult: vi.fn(() => false),
}))

vi.mock('../access', () => ({
  ensureWorkflowAccess: ensureWorkflowAccessMock,
  ensureWorkspaceAccess: vi.fn(),
  getDefaultWorkspaceId: vi.fn(),
}))

import { performUpdateWorkflow } from '@/lib/workflows/orchestration'
import { verifyFolderWorkspace } from '@/lib/workflows/utils'
import {
  executeMoveWorkflow,
  executeRunFromBlock,
  executeSetGlobalWorkflowVariables,
} from './mutations'

const performUpdateWorkflowMock = vi.mocked(performUpdateWorkflow)
const verifyFolderWorkspaceMock = vi.mocked(verifyFolderWorkspace)

describe('executeSetGlobalWorkflowVariables', () => {
  beforeEach(() => {
    vi.clearAllMocks()
    global.fetch = vi.fn().mockResolvedValue(new Response(null, { status: 200 })) as typeof fetch
    ensureWorkflowAccessMock.mockResolvedValue({
      workflow: {
        id: 'workflow-1',
        variables: {},
      },
    })
    setWorkflowVariablesMock.mockResolvedValue(undefined)
  })

  it('persists variable changes and notifies clients that workflow state changed', async () => {
    const result = await executeSetGlobalWorkflowVariables(
      {
        workflowId: 'workflow-1',
        operations: [{ operation: 'add', name: 'threshold', type: 'number', value: '5' }],
      },
      { userId: 'user-1' } as any
    )

    expect(result.success).toBe(true)
    const [, variables] = setWorkflowVariablesMock.mock.calls[0]
    expect(Object.values(variables)).toEqual([
      expect.objectContaining({
        workflowId: 'workflow-1',
        name: 'threshold',
        type: 'number',
        value: 5,
      }),
    ])
    expect(global.fetch).toHaveBeenCalledWith('http://socket.test/api/workflow-updated', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'x-api-key': 'secret',
      },
      body: JSON.stringify({ workflowId: 'workflow-1' }),
    })
    expect(recordAuditMock).toHaveBeenCalled()
  })
})

describe('lock enforcement', () => {
  beforeEach(() => {
    vi.clearAllMocks()
    global.fetch = vi.fn().mockResolvedValue(new Response(null, { status: 200 })) as typeof fetch
    workflowAuthzMockFns.mockAssertWorkflowMutable.mockResolvedValue(undefined)
    workflowAuthzMockFns.mockAssertFolderMutable.mockResolvedValue(undefined)
  })

  it('does not persist variable changes when the workflow is locked', async () => {
    ensureWorkflowAccessMock.mockResolvedValue({
      workflow: { id: 'workflow-1', variables: {} },
    })
    workflowAuthzMockFns.mockAssertWorkflowMutable.mockRejectedValueOnce(
      new Error('Workflow is locked')
    )

    const result = await executeSetGlobalWorkflowVariables(
      {
        workflowId: 'workflow-1',
        operations: [{ operation: 'add', name: 'threshold', type: 'number', value: '5' }],
      },
      { userId: 'user-1' } as any
    )

    expect(result.success).toBe(false)
    expect(result.error).toBe('Workflow is locked')
    expect(setWorkflowVariablesMock).not.toHaveBeenCalled()
  })

  it('does not move a workflow into a locked target folder', async () => {
    ensureWorkflowAccessMock.mockResolvedValue({
      workspaceId: 'workspace-1',
      workflow: { id: 'workflow-1', name: 'WF', folderId: null },
    })
    verifyFolderWorkspaceMock.mockResolvedValue(true)
    workflowAuthzMockFns.mockAssertFolderMutable.mockRejectedValueOnce(
      new Error('Folder is locked')
    )

    const result = await executeMoveWorkflow(
      { workflowIds: ['workflow-1'], folderId: 'locked-folder' },
      { userId: 'user-1' } as any
    )

    expect(result.success).toBe(false)
    expect(result.error).toBe('Folder is locked')
    expect(performUpdateWorkflowMock).not.toHaveBeenCalled()
  })
})

describe('executeRunFromBlock', () => {
  beforeEach(() => {
    vi.clearAllMocks()
    ensureWorkflowAccessMock.mockResolvedValue({
      workflow: {
        id: 'workflow-1',
        userId: 'owner-1',
        workspaceId: 'workspace-1',
        variables: {},
      },
    })
    executeWorkflowMock.mockResolvedValue({
      success: true,
      output: {},
      logs: [],
      metadata: { executionId: 'new-execution-1' },
    })
  })

  it('passes source execution lineage for stored run-from-block snapshots', async () => {
    const sourceSnapshot = {
      blockStates: {
        upstream: {
          output: {
            __simLargeValueRef: true,
            version: 1,
            id: 'lv_ABCDEFGHIJKL',
            kind: 'object',
            size: 10,
            key: 'execution/workspace-1/workflow-1/source-execution-1/large-value-lv_ABCDEFGHIJKL.json',
            executionId: 'source-execution-1',
          },
        },
      },
      executedBlocks: [],
      blockLogs: [],
      decisions: {},
      completedLoops: [],
      activeExecutionPath: [],
    }
    getExecutionStateForWorkflowMock.mockResolvedValue(sourceSnapshot)

    const result = await executeRunFromBlock(
      {
        workflowId: 'workflow-1',
        startBlockId: 'agent-1',
        executionId: 'source-execution-1',
      },
      { userId: 'user-1' } as any
    )

    expect(result.success).toBe(true)
    expect(executeWorkflowMock).toHaveBeenCalledWith(
      expect.any(Object),
      'request-1',
      undefined,
      'user-1',
      expect.objectContaining({
        runFromBlock: {
          startBlockId: 'agent-1',
          sourceSnapshot,
          sourceExecutionId: 'source-execution-1',
        },
      })
    )
  })
})
