import * as utils from '../../app/utils/index'
import { describe, it, expect, beforeEach, vi } from 'vitest'
import { useTaskDefinitions } from '~/composables/useTaskDefinitions'
import type { TaskDefinitionSummary } from '~/types/task_definitions'

vi.mock('~/composables/useNotification', () => {
  const success = vi.fn()
  const error = vi.fn()
  return {
    useNotification: () => ({ success, error }),
    default: () => ({ success, error })
  }
})

// Sample data
const summary: TaskDefinitionSummary = {
  id: 'abc',
  name: 'Test',
  priority: 1,
  updated_at: 123456,
}


// Helper to create a mock Response object
function createMockResponse({ ok, status, jsonData }: { ok: boolean, status: number, jsonData: any }) {
  return {
    ok,
    status,
    headers: new Headers(),
    redirected: false,
    statusText: '',
    type: 'basic',
    url: '',
    body: null,
    bodyUsed: false,
    clone() { return this },
    async json() { return jsonData },
    text: async () => JSON.stringify(jsonData),
    arrayBuffer: async () => new ArrayBuffer(0),
    blob: async () => new Blob(),
    formData: async () => new FormData(),
  } as Response
}

describe('useTaskDefinitions', () => {

  beforeEach(() => {
    vi.clearAllMocks()
  })

  it('sorts definitions by priority then name', async () => {
    const items = [
      { id: '1', name: 'B', priority: 2, updated_at: 1 },
      { id: '2', name: 'A', priority: 2, updated_at: 2 },
      { id: '3', name: 'C', priority: 1, updated_at: 3 },
    ]
    vi.spyOn(utils, 'request').mockResolvedValueOnce(createMockResponse({
      ok: true,
      status: 200,
      jsonData: items,
    }))
    const defs = useTaskDefinitions()
    await defs.loadDefinitions()
    expect(defs.definitions.value.map(d => d.id)).toEqual(['3', '2', '1'])
  })

  it('handles empty payload', async () => {
    vi.spyOn(utils, 'request').mockResolvedValueOnce(createMockResponse({
      ok: true,
      status: 200,
      jsonData: [],
    }))
    const defs = useTaskDefinitions()
    await defs.loadDefinitions()
    expect(defs.definitions.value).toEqual([])
    expect(defs.lastError.value).toBeNull()
  })

  it('handles malformed payload', async () => {
    vi.spyOn(utils, 'request').mockResolvedValueOnce({
      ok: true,
      json: async () => ({ jsonData: [{}] })
    })
    const defs = useTaskDefinitions()
    await expect(defs.loadDefinitions()).resolves.toBeUndefined()
  })

  it('handles malformed payload (throws when throwInstead is true)', async () => {
    vi.spyOn(utils, 'request').mockResolvedValueOnce({
      ok: true,
      json: async () => ({ jsonData: [{}] })
    })
    const defs = useTaskDefinitions()
    defs.throwInstead.value = true
    await expect(defs.loadDefinitions()).rejects.toThrow()
  })

  it('handles duplicate IDs (no deduplication, both present)', async () => {
    vi.spyOn(utils, 'request').mockResolvedValueOnce({
      ok: true,
      json: async () => [
        { id: 'dup', name: 'A', priority: 1 },
        { id: 'dup', name: 'B', priority: 2 },
      ]
    })
    const defs = useTaskDefinitions()
    await defs.loadDefinitions()
    expect(defs.definitions.value.length).toBe(2)
    expect(defs.definitions.value[0].name).toBe('A')
    expect(defs.definitions.value[1].name).toBe('B')
  })

  it('handles error on getDefinition', async () => {
    vi.spyOn(utils, 'request').mockResolvedValueOnce({
      ok: false,
      status: 404,
      statusText: 'Not Found',
      json: async () => ({})
    })
    const defs = useTaskDefinitions()
    await expect(defs.getDefinition('notfound')).rejects.toThrow('Request failed with status 404')
  })

  it('handles malformed getDefinition response', async () => {
    vi.spyOn(utils, 'request').mockResolvedValueOnce({
      ok: true,
      json: async () => ({})
    })
    const defs = useTaskDefinitions()
    await expect(defs.getDefinition('bad')).rejects.toThrow('Task definition response is missing definition payload.')
  })

  it('handles error on createDefinition', async () => {
    vi.spyOn(utils, 'request').mockResolvedValueOnce({
      ok: false,
      status: 400,
      statusText: 'Bad Request',
      json: async () => ({})
    })
    const defs = useTaskDefinitions()
    await expect(defs.createDefinition({ id: 'fail', name: 'Fail', priority: 1 })).rejects.toThrow('Request failed with status 400')
  })

  it('handles error on updateDefinition', async () => {
    vi.spyOn(utils, 'request').mockResolvedValueOnce({
      ok: false,
      status: 400,
      statusText: 'Bad Request',
      json: async () => ({})
    })
    const defs = useTaskDefinitions()
    await expect(defs.updateDefinition({ id: 'fail', name: 'Fail', priority: 1 })).rejects.toThrow('Request failed with status 400')
  })

  it('handles error on deleteDefinition', async () => {
    vi.spyOn(utils, 'request').mockResolvedValueOnce({
      ok: false,
      status: 400,
      statusText: 'Bad Request',
      json: async () => ({})
    })
    const defs = useTaskDefinitions()
    await expect(defs.deleteDefinition('fail')).rejects.toThrow('Request failed with status 400')
  })

  it('loads definitions successfully (duplicate test)', async () => {
    vi.spyOn(utils, 'request').mockResolvedValueOnce(createMockResponse({
      ok: true,
      status: 200,
      jsonData: [summary],
    }))
    const defs = useTaskDefinitions()
    await defs.loadDefinitions()
    expect(defs.definitions.value).toEqual([summary])
    expect(defs.lastError.value).toBeNull()
  })


  it('calls success notification on createDefinition', async () => {
    vi.spyOn(utils, 'request').mockResolvedValueOnce({
      ok: true,
      json: async () => ({})
    })
    const defs = useTaskDefinitions()
    await defs.createDefinition({ id: 'new', name: 'New', priority: 1 })
    // Access the spy directly from the mock
    const notify = (await import('~/composables/useNotification')).useNotification()
    expect(notify.success).toHaveBeenCalledWith('Task definition created.')
  })
});
