import type { Message } from '../src/dimsdk/core/model.ts'
import { afterEach, describe, expect, it, vi } from 'vitest'

const {
  execFileSyncMock,
  mkdirSyncMock,
  realpathSyncMock,
  rmSyncMock,
  writeFileSyncMock,
} = vi.hoisted(() => ({
  execFileSyncMock: vi.fn(),
  mkdirSyncMock: vi.fn(),
  realpathSyncMock: vi.fn((input: string) => input),
  rmSyncMock: vi.fn(),
  writeFileSyncMock: vi.fn(),
}))

vi.mock('node:child_process', () => ({
  execFileSync: execFileSyncMock,
}))

vi.mock('node:fs', () => ({
  mkdirSync: mkdirSyncMock,
  realpathSync: realpathSyncMock,
  rmSync: rmSyncMock,
  writeFileSync: writeFileSyncMock,
}))

const { createFileRevertHandlers } = await import('../src/dimsdk/core/store/file-reverts.ts')

function createAssistantMessage(parts: Message[]): Extract<Message, { role: 'assistant' }> {
  const assistant = parts[0]
  if (!assistant || assistant.role !== 'assistant')
    throw new Error('expected assistant message')
  return assistant
}

afterEach(() => {
  execFileSyncMock.mockReset()
  mkdirSyncMock.mockReset()
  realpathSyncMock.mockClear()
  rmSyncMock.mockReset()
  writeFileSyncMock.mockReset()
})

describe('file revert handlers', () => {
  it('collects changed files from llm meta without legacy casts', () => {
    execFileSyncMock.mockImplementation((_cmd: string, args: string[]) => {
      if (args.includes('rev-parse'))
        return '/repo\n'
      return ''
    })

    const message = createAssistantMessage([
      {
        id: 'assistant-1',
        role: 'assistant',
        parts: [
          {
            type: 'tool_call',
            call: {
              id: 'tool-1',
              name: 'Edit',
              arguments: {},
            },
          },
          {
            type: 'llm_meta',
            durationMs: 1,
            changedFileDetails: [
              { path: '/repo/src/a.ts' },
              { path: '/repo/src/b.ts' },
            ],
            changedFiles: ['/repo/src/a.ts', '/repo/src/c.ts'],
          },
        ],
      },
    ])

    const handlers = createFileRevertHandlers({
      state: {
        messages: [message],
        context: { cwd: '/repo' },
        revertedMessages: [],
        revertedFilesApplied: false,
        fileRevertsByToolCallId: new Map(),
      },
      debugLog: () => {},
      revertFromMessageBase: () => 0,
      restoreRevertedMessagesBase: () => 0,
      clearRevertedMessagesBase: () => {},
    })

    expect(handlers.revertFilesFromMessage('assistant-1')).toBe(3)
    expect(execFileSyncMock).toHaveBeenCalledWith(
      'git',
      [
        '-C',
        '/repo',
        'restore',
        '--worktree',
        '--source=HEAD',
        '--',
        'src/a.ts',
        'src/b.ts',
        'src/c.ts',
      ],
      { stdio: 'ignore' },
    )
  })

  it('falls back to stored file revert snapshots when git restore is unavailable', () => {
    execFileSyncMock.mockImplementation((_cmd: string, args: string[]) => {
      if (args.includes('rev-parse'))
        throw new Error('not a git repo')
      return ''
    })

    const message = createAssistantMessage([
      {
        id: 'assistant-2',
        role: 'assistant',
        parts: [
          {
            type: 'tool_call',
            call: {
              id: 'tool-2',
              name: 'Edit',
              arguments: {},
            },
          },
        ],
      },
    ])

    const handlers = createFileRevertHandlers({
      state: {
        messages: [message],
        context: { cwd: '/repo' },
        revertedMessages: [],
        revertedFilesApplied: false,
        fileRevertsByToolCallId: new Map([
          ['tool-2', {
            filePath: '/repo/src/generated.ts',
            existedBefore: false,
            previousContent: null,
            nextContent: 'next',
          }],
        ]),
      },
      debugLog: () => {},
      revertFromMessageBase: () => 0,
      restoreRevertedMessagesBase: () => 0,
      clearRevertedMessagesBase: () => {},
    })

    expect(handlers.revertFilesFromMessage('assistant-2')).toBe(1)
    expect(rmSyncMock).toHaveBeenCalledWith('/repo/src/generated.ts', {
      force: true,
    })
  })
})
