import { test } from '@japa/runner'
import sinon from 'sinon'
import { HttpProject } from '../../../src/models/HttpProject.js'
import { ProjectFolder, DefaultFolderName, IProjectFolder } from '../../../src/models/ProjectFolder.js'
import { ProjectItem } from '../../../src/models/ProjectItem.js'
import { Environment, Kind as EnvironmentKind } from '../../../src/models/Environment.js'
import { ProjectRequest } from '../../../src/models/ProjectRequest.js'
import { ProjectFolderKind, ThingKind } from '../../../src/models/kinds.js'

test.group('Models > ProjectFolder > Initialization', (group) => {
  let project: HttpProject
  group.each.setup(() => {
    project = new HttpProject()
  })

  test('initializes a default folder', ({ assert }) => {
    const result = new ProjectFolder(project)
    assert.equal(result.kind, ProjectFolderKind, 'sets the kind property')
    assert.deepEqual(result.items, [], 'sets the items property')
    assert.typeOf(result.updated, 'number', 'sets the updated property')
    assert.typeOf(result.created, 'number', 'sets the created property')

    const { info } = result
    assert.typeOf(info, 'EventTarget', 'sets the default info property')
    assert.equal(info.kind, ThingKind, 'sets the info.kind property')
    assert.equal(info.name, DefaultFolderName, 'sets the info.name property')
  })
})

test.group('Models > ProjectFolder > From schema initialization', (group) => {
  let project: HttpProject
  let base: IProjectFolder
  group.each.setup(() => {
    project = new HttpProject()
    base = {
      kind: ProjectFolderKind,
      items: [],
      updated: 123,
      created: 456,
      key: 'test1234',
      info: {
        kind: ThingKind,
        name: '',
      },
    }
  })

  test('sets the info', ({ assert }) => {
    const init: IProjectFolder = {
      ...base,
      ...{
        info: {
          kind: ThingKind,
          name: 'Test project',
          description: 'Project description',
          version: '1.2.3',
        },
      },
    }
    const result = new ProjectFolder(project, init)
    const { info } = result
    assert.equal(info.kind, ThingKind, 'sets the info.kind property')
    assert.equal(info.name, 'Test project', 'sets the info.name property')
    assert.equal(info.description, 'Project description', 'sets the info.description property')
    assert.equal(info.version, '1.2.3', 'sets the info.version property')
  })

  test('sets the created/updated', ({ assert }) => {
    const result = new ProjectFolder(project, base)
    assert.equal(result.created, 456)
    assert.equal(result.updated, 123)
  })

  test('sets the passed key', ({ assert }) => {
    const result = new ProjectFolder(project, base)
    assert.equal(result.key, 'test1234')
  })

  test('sets a new key when the passed key is missing', ({ assert }) => {
    // @ts-expect-error Used in testing
    delete base.key
    const result = new ProjectFolder(project, base)
    assert.typeOf(result.key, 'string')
  })

  test('creates the default items', ({ assert }) => {
    // @ts-expect-error Used in testing
    delete base.items
    const result = new ProjectFolder(project, base)
    assert.deepEqual(result.items, [])
  })

  test('sets the stored items', ({ assert }) => {
    base.items = [ProjectItem.projectFolder(project, 'a-key')]
    const serialized = JSON.stringify(base)
    const result = new ProjectFolder(project, serialized)
    assert.lengthOf(result.items, 1, 'has a single item')
    assert.equal(result.items[0].key, 'a-key', 'has the serialized item')
  })
})

test.group('Models > ProjectFolder > From JSON string initialization', () => {
  test('restores project data from JSON string', ({ assert }) => {
    const project = new HttpProject()
    const folder = project.addFolder('a folder')
    const str = JSON.stringify(folder)

    const result = new ProjectFolder(project, str)

    assert.equal(result.key, folder.key, 'restores the key')
    assert.equal(result.info.name, 'a folder', 'restores the info object')
  })

  test('throws when invalid folder object', ({ assert }) => {
    const project = new HttpProject()
    const folder = project.addFolder('a folder')
    const schema = folder.toJSON()
    // @ts-expect-error Used in testing
    delete schema.kind
    const str = JSON.stringify(schema)

    assert.throws(() => {
      new ProjectFolder(project, str)
    })
  })
})

test.group('Models > ProjectFolder > toJSON()', (group) => {
  let project: HttpProject
  let base: IProjectFolder
  group.each.setup(() => {
    project = new HttpProject()
    base = {
      kind: ProjectFolderKind,
      items: [],
      updated: 123,
      created: 456,
      key: 'test1234',
      info: {
        kind: ThingKind,
        name: '',
      },
    }
  })

  test('serializes the info object', ({ assert }) => {
    const init: IProjectFolder = {
      ...base,
      ...{
        info: {
          kind: ThingKind,
          name: 'Test project',
          description: 'Project description',
          version: '1.2.3',
        },
      },
    }
    const folder = new ProjectFolder(project, init)
    const result = folder.toJSON()
    assert.equal(result.info.kind, ThingKind, 'has the kind')
    assert.equal(result.info.name, 'Test project', 'has the name')
    assert.equal(result.info.description, 'Project description', 'has the description')
    assert.equal(result.info.version, '1.2.3', 'has the version')
  })

  test('serializes the key', ({ assert }) => {
    const init: IProjectFolder = { ...base }
    const folder = new ProjectFolder(project, init)
    const result = folder.toJSON()
    assert.equal(result.key, init.key)
  })

  test('serializes the created/updated', ({ assert }) => {
    const init: IProjectFolder = { ...base }
    const folder = new ProjectFolder(project, init)
    const result = folder.toJSON()
    assert.equal(result.created, init.created)
    assert.equal(result.updated, init.updated)
  })

  test('serializes the items', ({ assert }) => {
    const init: IProjectFolder = { ...base, ...{ items: [ProjectItem.projectFolder(project, 'a-key')] } }
    const folder = new ProjectFolder(project, init)
    const result = folder.toJSON()
    assert.lengthOf(result.items, 1)
  })
})

test.group('Models > ProjectFolder > ProjectFolder.fromName()', (group) => {
  let project: HttpProject
  group.each.setup(() => {
    project = new HttpProject()
  })

  test('sets the name', ({ assert }) => {
    const result = ProjectFolder.fromName(project, 'a name')
    assert.equal(result.info.name, 'a name')
  })

  test('uses the default name', ({ assert }) => {
    const result = ProjectFolder.fromName(project)
    assert.equal(result.info.name, DefaultFolderName)
  })

  test('generates the key', ({ assert }) => {
    const result = ProjectFolder.fromName(project, 'a name')
    assert.typeOf(result.key, 'string')
  })

  test('generates the created/updated', ({ assert }) => {
    const result = ProjectFolder.fromName(project, 'a name')
    assert.closeTo(result.updated, Date.now(), 100)
    assert.closeTo(result.created, Date.now(), 100)
  })

  test('adds empty items', ({ assert }) => {
    const result = ProjectFolder.fromName(project, 'a name')
    assert.deepEqual(result.items, [])
  })

  test('sets the kind', ({ assert }) => {
    const result = ProjectFolder.fromName(project, 'a name')
    assert.equal(result.kind, ProjectFolderKind)
  })
})

test.group('Models > ProjectFolder > new()', () => {
  test('restores a folder definition', ({ assert }) => {
    const project = new HttpProject()
    const folder = project.addFolder('a folder')
    const time = 12345
    folder.new({
      created: time,
      updated: time,
      info: {
        kind: ThingKind,
        name: 'test',
      },
      items: [],
      key: 'abc',
      kind: ProjectFolderKind,
    })
    assert.equal(folder.created, time, 'updates the created')
    assert.equal(folder.updated, time, 'updates the updated') // Corrected from 'updates the created'
    assert.equal(folder.key, 'abc', 'updates the key')
    assert.equal(folder.info.name, 'test', 'updates the info')
  })

  test('restores items', ({ assert }) => {
    const project = new HttpProject()
    const folder = project.addFolder('a folder')
    const time = 12345
    const def: IProjectFolder = {
      created: time,
      updated: time,
      info: {
        kind: ThingKind,
        name: 'test',
      },
      items: [], // Assuming items should be processed by .new() if present in schema
      key: 'abc',
      kind: ProjectFolderKind,
    }
    folder.new(def)
    assert.equal(folder.created, time, 'updates the created')
    assert.equal(folder.updated, time, 'updates the updated') // Corrected
    assert.equal(folder.key, 'abc', 'updates the key')
    assert.equal(folder.info.name, 'test', 'updates the info')
    // Add assertion for items if .new() is expected to process them
    assert.deepEqual(folder.items, [], 'updates the items')
  })

  test('adds the default info object', ({ assert }) => {
    const project = new HttpProject()
    const folder = project.addFolder('a folder')
    const time = 12345
    const def: IProjectFolder = {
      created: time,
      updated: time,
      info: {
        // This info will be deleted before calling new()
        kind: ThingKind,
        name: 'test',
      },
      items: [],
      key: 'abc',
      kind: ProjectFolderKind,
    }
    // @ts-expect-error Used in testing
    delete def.info
    folder.new(def)
    assert.equal(folder.info.name, DefaultFolderName)
  })
})

test.group('Models > ProjectFolder > addFolder()', (group) => {
  let project: HttpProject
  group.each.setup(() => {
    project = new HttpProject()
  })

  test("calls the project's add folder function", ({ assert }) => {
    const parent = project.addFolder('parent')
    const spy = sinon.spy(project, 'addFolder')
    parent.addFolder('a folder')
    assert.isTrue(spy.calledOnce)
    assert.equal(spy.args[0][0], 'a folder', 'has the name')
    assert.deepEqual(spy.args[0][1], { parent: parent.key }, 'has the options')
    spy.restore()
  })

  test('adds the folder to the items', ({ assert }) => {
    const parent = project.addFolder('parent')
    parent.addFolder('sub')
    assert.lengthOf(parent.items, 1)
  })

  test('adds a folder by name', ({ assert }) => {
    const parent = project.addFolder('parent')
    parent.addFolder('sub')
    const def = project.findFolder('sub')!
    assert.equal(def.info.name, 'sub')
  })

  test('adds a folder by schema', ({ assert }) => {
    const parent = project.addFolder('parent')
    const f = new ProjectFolder(project)
    f.info.name = 'sub'
    parent.addFolder(f.toJSON())
    const def = project.findFolder('sub')!
    assert.equal(def.info.name, 'sub')
  })

  test('adds a folder by an instance', ({ assert }) => {
    const parent = project.addFolder('parent')
    const f = new ProjectFolder(project)
    f.info.name = 'sub'
    parent.addFolder(f)
    const def = project.findFolder('sub')!
    assert.equal(def.info.name, 'sub')
  })
})

test.group('Models > ProjectFolder > addRequest()', (group) => {
  let project: HttpProject
  group.each.setup(() => {
    project = new HttpProject()
  })

  test("calls the project's add request function", ({ assert }) => {
    const parent = project.addFolder('parent')
    const spy = sinon.spy(project, 'addRequest')
    const request = ProjectRequest.fromName('test', project)
    parent.addRequest(request)
    assert.isTrue(spy.calledOnce)
    assert.deepEqual(spy.args[0][0], request, 'has the request')
    assert.deepEqual(spy.args[0][1], { parent: parent.key }, 'has the options')
    spy.restore()
  })

  test('adds the request to the items', ({ assert }) => {
    const parent = project.addFolder('parent')
    const request = ProjectRequest.fromName('test', project)
    parent.addRequest(request)
    assert.lengthOf(parent.items, 1)
  })

  test('adds a request from the url', ({ assert }) => {
    const parent = project.addFolder('parent')
    const request = parent.addRequest('https://api.com')
    assert.equal(request.info.name, 'https://api.com')
    assert.equal(request.expects.url, 'https://api.com')
  })
})

test.group('Models > ProjectFolder > listFolderItems()', (group) => {
  let project: HttpProject
  group.each.setup(() => {
    project = new HttpProject()
  })

  test('returns empty array when no folders', ({ assert }) => {
    const folder = project.addFolder('parent')

    const request = ProjectRequest.fromName('test', project)
    folder.addRequest(request)

    const result = folder.listFolderItems()
    assert.deepEqual(result, [])
  })

  test('returns folder folders', ({ assert }) => {
    const folder = project.addFolder('parent')
    const f1 = folder.addFolder('f1')
    const f2 = folder.addFolder('f2')

    const request = ProjectRequest.fromName('test', project)
    folder.addRequest(request)

    const result = folder.listFolderItems()
    assert.lengthOf(result, 2, 'has 2 items')
    assert.equal(result[0].key, f1.key, 'has the first folder')
    assert.equal(result[1].key, f2.key, 'has the second folder')
  })
})

test.group('Models > ProjectFolder > listRequestItems()', (group) => {
  let project: HttpProject
  group.each.setup(() => {
    project = new HttpProject()
  })

  test('returns empty array when no requests', ({ assert }) => {
    const folder = project.addFolder('parent')
    folder.addFolder('f1')

    const result = folder.listRequestItems()
    assert.deepEqual(result, [])
  })

  test('returns folder requests', ({ assert }) => {
    const folder = project.addFolder('parent')
    const request = ProjectRequest.fromName('test', project)
    folder.addRequest(request)

    const result = folder.listRequestItems()
    assert.lengthOf(result, 1, 'has the request')
    assert.equal(result[0].key, request.key, 'has the key')
  })
})

test.group('Models > ProjectFolder > listFolders()', (group) => {
  let project: HttpProject
  group.each.setup(() => {
    project = new HttpProject()
  })

  test("calls the project's listFolders function", ({ assert }) => {
    const folder = project.addFolder('parent')
    const spy = sinon.spy(project, 'listFolders')
    folder.listFolders()
    assert.isTrue(spy.calledOnce)
    assert.deepEqual(spy.args[0][0], { folder: folder.key }, 'has the only argument')
    // Original was spy.args[0][0]!.folder
    spy.restore()
  })

  test('returns the list', ({ assert }) => {
    const folder = project.addFolder('parent')
    const child = folder.addFolder('child')
    const result = folder.listFolders()
    assert.deepEqual(result, [child])
  })
})

test.group('Models > ProjectFolder > listRequests()', (group) => {
  let project: HttpProject
  group.each.setup(() => {
    project = new HttpProject()
  })

  test("calls the project's listRequests function", ({ assert }) => {
    const folder = project.addFolder('parent')
    const spy = sinon.spy(project, 'listRequests')
    folder.listRequests()
    assert.isTrue(spy.calledOnce)
    assert.equal(spy.args[0][0], folder.key, 'has the only argument')
    spy.restore()
  })

  test('returns the list', ({ assert }) => {
    const folder = project.addFolder('parent')

    const request = ProjectRequest.fromName('test', project)
    folder.addRequest(request)

    const result = folder.listRequests()
    assert.deepEqual(result, [request])
  })
})

test.group('Models > ProjectFolder > remove()', (group) => {
  let project: HttpProject
  let folder: ProjectFolder

  group.each.setup(() => {
    project = new HttpProject()
    folder = project.addFolder('test')
  })

  test('removes the folder from the project definitions', ({ assert }) => {
    folder.remove()
    assert.deepEqual(project.definitions.folders, [])
  })

  test('removes the folder from the project items', ({ assert }) => {
    folder.remove()
    assert.deepEqual(project.items, [])
  })

  test('removes the folder from the parent folder items', ({ assert }) => {
    const sub = folder.addFolder('sub')
    sub.remove()
    assert.deepEqual(folder.items, [])
  })

  test('removes the sub-folder from the project definitions', ({ assert }) => {
    const sub = folder.addFolder('sub')
    assert.lengthOf(project.definitions.folders, 2, 'has 2 definitions')
    sub.remove()
    assert.lengthOf(project.definitions.folders, 1, 'has 1 definition')
  })
})

test.group('Models > ProjectFolder > getParent()', (group) => {
  let project: HttpProject
  group.each.setup(() => {
    project = new HttpProject()
  })

  test('returns the project object', ({ assert }) => {
    const folder = project.addFolder('test')
    const result = folder.getParent()
    assert.isTrue(result === project)
  })

  test('returns the folder object', ({ assert }) => {
    const parent = project.addFolder('test')
    const folder = parent.addFolder('test 2')
    const result = folder.getParent()
    assert.isTrue(result === parent)
  })
})

test.group('Models > ProjectFolder > getProject()', (group) => {
  let project: HttpProject
  group.each.setup(() => {
    project = new HttpProject()
  })

  test('returns the project when added to the project', ({ assert }) => {
    const folder = project.addFolder('test')
    const result = folder.getProject()
    assert.isTrue(result === project)
  })

  test('returns the project when added to a folder', ({ assert }) => {
    const parent = project.addFolder('test')
    const folder = parent.addFolder('test 2')
    const result = folder.getProject()
    assert.isTrue(result === project)
  })
})

test.group('Models > ProjectFolder > clone()', (group) => {
  let project: HttpProject
  let folder: ProjectFolder

  group.each.setup(() => {
    project = new HttpProject()
    folder = project.addFolder('test')
  })

  test('updates the key by default', ({ assert }) => {
    const { key: oldKey } = folder
    const copy = folder.clone()
    assert.typeOf(copy.key, 'string', 'has the key')
    assert.notEqual(copy.key, oldKey, 'has a new key')
  })

  test('adds the copy to the project definitions (project root)', ({ assert }) => {
    const copy = folder.clone()
    assert.lengthOf(project.definitions.folders, 2, 'has 2 definitions')
    assert.isTrue(
      project.definitions.folders.some((i) => i.key === copy.key),
      'has the folder definition'
    )
  })

  test('adds the copy to the project definitions (folder root)', ({ assert }) => {
    const sub = folder.addFolder('sub')
    const copy = sub.clone()
    assert.lengthOf(project.definitions.folders, 3, 'has 3 definitions')
    assert.isTrue(
      project.definitions.folders.some((i) => i.key === copy.key),
      'has the folder definition'
    )
  })

  test('adds the copy to the project items (project root)', ({ assert }) => {
    const copy = folder.clone()
    assert.lengthOf(project.items, 2, 'has 2 items')
    assert.isTrue(
      project.items.some((i) => i.key === copy.key),
      'has the folder item'
    )
  })

  test('adds the copy to the sub-folder items', ({ assert }) => {
    const sub = folder.addFolder('sub')
    const copy = sub.clone()
    assert.lengthOf(project.items, 1, 'project has 1 item')
    assert.lengthOf(folder.items, 2, 'parent has 2 items')
    assert.isTrue(
      folder.items.some((i) => i.key === copy.key),
      'the folder is added to the parent'
    )
  })

  test('copies requests with the folder by default', ({ assert }) => {
    const r = folder.addRequest('https://copy.com')
    const copy = folder.clone()

    assert.lengthOf(copy.items, 1, 'the copy has one request')
    assert.typeOf(copy.items[0].key, 'string', 'the request has the key')
    assert.notEqual(copy.items[0].key, r.key, 'the copied request has a different key')
  })

  test('adds the copied folder and request to the project definitions', ({ assert }) => {
    folder.addRequest('https://copy.com')
    folder.clone()

    assert.lengthOf(project.definitions.requests, 2, 'the project has 2 request definitions')
    assert.lengthOf(project.definitions.folders, 2, 'the project has 2 folder definitions')
    // Corrected from project.definitions.requests

    const [f1, f2] = project.definitions.folders
    const [r1, r2] = project.definitions.requests

    assert.notEqual(f1.key, f2.key, 'folder keys are different')
    assert.notEqual(r1.key, r2.key, 'request keys are different')
  })

  test('quietly ignores missing requests', ({ assert }) => {
    const r = folder.addRequest('https://copy.com')
    const i = project.definitions.requests.findIndex((item) => item.key === r.key)
    project.definitions.requests.splice(i, 1)
    folder.clone()
    assert.lengthOf(project.definitions.folders, 2, 'the project has 2 definitions')
    const [f1, f2] = project.definitions.folders
    assert.notEqual(f1.key, f2.key, 'folder keys are different')
  })

  test('copies sub-folders with the folder by default', ({ assert }) => {
    const sub = folder.addFolder('sub')
    const copy = folder.clone()

    assert.lengthOf(copy.items, 1, 'the copy has one item') // Corrected from 'one request'
    assert.typeOf(copy.items[0].key, 'string', 'the folder has the key')
    assert.notEqual(copy.items[0].key, sub.key, 'the copied folder has a different key')
  })

  test('adds a sub-folder of the copied folder to the project definitions', ({ assert }) => {
    folder.addFolder('sub orig')
    folder.clone()

    assert.lengthOf(project.definitions.folders, 4, 'the project has 4 folder definitions')

    // It's better to find them by known properties if order is not guaranteed
    const originalMainFolder = project.definitions.folders.find((f) => f.info.name === 'test' && f.key === folder.key)
    const originalSubFolder = project.definitions.folders.find((f) => f.info.name === 'sub orig')
    const clonedMainFolder = project.definitions.folders.find((f) => f.info.name === 'test' && f.key !== folder.key)
    const clonedSubFolder = project.definitions.folders.find(
      (f) => f.info.name === 'sub orig' && f.key !== originalSubFolder!.key
    )

    assert.ok(originalMainFolder, 'Original main folder found')
    assert.ok(originalSubFolder, 'Original sub folder found')
    assert.ok(clonedMainFolder, 'Cloned main folder found')
    assert.ok(clonedSubFolder, 'Cloned sub folder found')

    assert.notEqual(originalMainFolder!.key, clonedMainFolder!.key, 'folder keys are different')
    assert.equal(originalMainFolder!.info.name, clonedMainFolder!.info.name, 'folder 1 names match')
    // Corrected from 'request keys'
    assert.notEqual(originalSubFolder!.key, clonedSubFolder!.key, 'sub-folder keys are different')
    assert.equal(originalSubFolder!.info.name, clonedSubFolder!.info.name, 'folder 2 names match')
  })

  test('quietly ignores missing folders', ({ assert }) => {
    const sub = folder.addFolder('sub')
    const i = project.definitions.folders.findIndex((item) => item.key === sub.key)
    project.definitions.folders.splice(i, 1)
    folder.clone()
    assert.lengthOf(project.definitions.folders, 2, 'the project has 2 definitions')
    const [f1, f2] = project.definitions.folders
    assert.notEqual(f1.key, f2.key, 'folder keys are different')
  })

  test('copies a folder with a sub-folder with requests', ({ assert }) => {
    const sub = folder.addFolder('sub')
    const origRequest = sub.addRequest('http://api.com')

    const result = folder.clone()

    const projectFolders = project.listFolders() // This lists all folders in the project
    // After clone, project should have original 'test' folder and its clone.
    // 'sub' is inside 'test', and its clone is inside the clone of 'test'.
    // So, project.listFolders() without arguments should list 'test' and its clone.
    assert.lengthOf(
      projectFolders.filter((pf) => pf.getParent() === project),
      2,
      'the project has 2 top-level folders'
    )

    const originalTestFolder = projectFolders.find((pf) => pf.key === folder.key)
    const clonedTestFolder = projectFolders.find((pf) => pf.key === result.key)

    assert.deepEqual(originalTestFolder, folder, 'folder #1 is set')
    assert.deepEqual(clonedTestFolder, result, 'folder #2 is set')

    const originalSubFolder = originalTestFolder!.listFolders()[0]
    assert.lengthOf(originalSubFolder.listRequests(), 1, 'original sub-folder has a single request')
    assert.deepEqual(originalSubFolder.listRequests()[0], origRequest, 'original sub-folder has the original request')

    const topRequests = result.listRequests()
    assert.lengthOf(topRequests, 0, 'root copy folder has no requests')

    const foldersInCloned = result.listFolders()
    assert.lengthOf(foldersInCloned, 1, 'root copy folder has a folder')

    const [subCopy] = foldersInCloned
    const subRequests = subCopy.listRequests()
    assert.lengthOf(subRequests, 1, 'sub folder has a request')
  })

  test('copies a folder to a specific folder inside the same project', ({ assert }) => {
    const sub = folder.addFolder('sub1')
    sub.addRequest('http://api.com')
    const targetFolder = project.addFolder('sub2') // This is a top-level folder
    const clonedFolderInTarget = folder.clone({ targetFolder: targetFolder.key })

    const subFoldersInTarget = targetFolder.listFolders()
    assert.lengthOf(subFoldersInTarget, 1, 'has the copied folder')
    const [copyTopLevelFolder] = subFoldersInTarget
    assert.equal(copyTopLevelFolder.key, clonedFolderInTarget.key)
    assert.equal(copyTopLevelFolder.info.name, folder.info.name)

    const [subLevelCopy] = copyTopLevelFolder.listFolders()
    assert.ok(subLevelCopy, 'has the copied sub-folder')
    assert.equal(subLevelCopy.info.name, sub.info.name)

    const subRequests = subLevelCopy.listRequests()
    assert.lengthOf(subRequests, 1, 'has the copied request')
  })

  test('copies a folder to another project to the root level', ({ assert }) => {
    const sub = folder.addFolder('sub')
    const origRequest = sub.addRequest('http://api.com')

    const origSnapshot = project.toJSON()

    const targetProject = new HttpProject()
    const clonedInTargetProject = folder.clone({ targetProject })

    assert.deepEqual(project.toJSON(), origSnapshot, 'the original project is not changed')

    const copiedTopFolder = targetProject.findFolder(folder.info.name!)!
    assert.ok(copiedTopFolder, 'target project has the top-level folder')
    assert.equal(copiedTopFolder.key, clonedInTargetProject.key)
    assert.notEqual(copiedTopFolder.key, folder.key, 'target folder has a new key')

    const subFolderInTarget = copiedTopFolder.listFolders()[0]
    assert.ok(subFolderInTarget, 'target project has the sub-folder')
    assert.notEqual(subFolderInTarget.key, sub.key, 'the sub-folder has a new key')

    const copiedRequest = subFolderInTarget.listRequests()[0]
    assert.ok(copiedRequest, 'target project has the request')
    assert.notEqual(copiedRequest.key, origRequest.key, 'the request has a new key')
  })

  test('copies a folder to another project under a folder', ({ assert }) => {
    const sub = folder.addFolder('sub')
    sub.addRequest('http://api.com')

    const origSnapshot = project.toJSON()

    const targetProject = new HttpProject()
    const targetFolderInTargetProject = targetProject.addFolder('parent')
    folder.clone({ targetProject, targetFolder: targetFolderInTargetProject.key })

    assert.deepEqual(project.toJSON(), origSnapshot, 'the original project is not changed')

    // the target project has 'parent' folder, then 'test' (cloned) inside 'parent', then 'sub' (cloned) inside 'test'.
    // So 3 folder definitions. And 1 request definition.
    assert.lengthOf(targetProject.definitions.folders, 3, 'has the copied folder definitions')
    assert.lengthOf(targetProject.definitions.requests, 1, 'has the copied requests definitions')

    const rootFoldersInTarget = targetFolderInTargetProject.listFolders()
    assert.lengthOf(rootFoldersInTarget, 1, 'the target folder has a root folder from the originating project')

    const [rootFolderCopy] = rootFoldersInTarget
    const subFoldersInCopy = rootFolderCopy.listFolders()
    assert.lengthOf(subFoldersInCopy, 1, 'the copied folder has a sub-folder')

    const [subFolderCopy] = subFoldersInCopy

    const requestsInSubCopy = subFolderCopy.listRequests()
    assert.lengthOf(requestsInSubCopy, 1, 'the sub-folder has a request')
  })

  test('throws when unable to find a parent in the same project', ({ assert }) => {
    const sub = folder.addFolder('sub')
    sub.addRequest('http://api.com')
    assert.throws(() => {
      folder.clone({ targetFolder: 'test-non-existent' })
    })
  })

  test('throws when unable to find a parent in the foreign project', ({ assert }) => {
    const sub = folder.addFolder('sub')
    sub.addRequest('http://api.com')

    const targetProject = new HttpProject()
    assert.throws(() => {
      folder.clone({ targetProject, targetFolder: 'test-non-existent' })
    })
  })
})

test.group('Models > ProjectFolder > addEnvironment()', (group) => {
  let project: HttpProject
  let f1: ProjectFolder
  group.each.setup(() => {
    project = HttpProject.fromName('test')
    f1 = project.addFolder('f1')
  })

  test('adds environment by name', ({ assert }) => {
    const created = f1.addEnvironment('test')
    assert.lengthOf(f1.items, 1, 'has an item')
    assert.equal(f1.items[0].key, created.key, 'has the item key')
    assert.equal(f1.items[0].kind, EnvironmentKind, 'has the item kind')
  })

  test('adds environment from an instance', ({ assert }) => {
    const created = Environment.fromName('test')
    f1.addEnvironment(created)
    assert.lengthOf(f1.items, 1, 'has an item')
    assert.equal(f1.items[0].key, created.key, 'has the item key')
  })

  test('adds environment from a schema', ({ assert }) => {
    const created = Environment.fromName('test')
    f1.addEnvironment(created.toJSON())
    assert.lengthOf(f1.items, 1, 'has an item')
    assert.equal(f1.items[0].key, created.key, 'has the item key')
  })
})

test.group('Models > ProjectFolder > getEnvironments()', () => {
  test('returns project class initialization environments', ({ assert }) => {
    const project = new HttpProject(undefined, [Environment.fromName('test').toJSON()])
    const f1 = project.addFolder('f1') // Corrected: addFolder needs project instance
    f1.addEnvironment(Environment.fromName('other'))
    const envs = f1.getEnvironments()
    assert.typeOf(envs, 'array')
    assert.lengthOf(envs, 1)
    assert.equal(envs[0].info.name, 'test')
  })

  test('returns environments when no initialization environments', ({ assert }) => {
    const project = new HttpProject()
    const f1 = project.addFolder('f1')
    const e1 = f1.addEnvironment(Environment.fromName('other'))
    project.addEnvironment('e2') // This is a root environment, getEnvironments on folder should not see it.

    const result = f1.getEnvironments()
    assert.deepEqual(result, [e1])
  })
})

test.group('Models > ProjectFolder > listEnvironments()', (group) => {
  let project: HttpProject
  let f1: ProjectFolder
  let e2: Environment
  group.each.setup(() => {
    project = new HttpProject()
    project.addEnvironment('e1') // Root environment
    f1 = project.addFolder('f1')
    e2 = f1.addEnvironment('e2') // Environment in folder f1
  })

  test('returns environments defined in the folder', ({ assert }) => {
    // This method is on HttpProject, not ProjectFolder.
    // It lists environments associated with a specific parent (folder key or project).
    const result = project.listEnvironments({ parent: f1.key })
    assert.deepEqual(result, [e2])
  })

  test('ignores sub-folders', ({ assert }) => {
    const f2 = f1.addFolder('f2')
    f2.addEnvironment('e3')
    const result = project.listEnvironments({ parent: f1.key })
    assert.deepEqual(result, [e2]) // Should only list e2, as e3 is in f2, not directly in f1.
  })
})

test.group('Models > ProjectFolder > removeEnvironment()', (group) => {
  let project: HttpProject
  let f1: ProjectFolder
  let e2: Environment
  group.each.setup(() => {
    project = new HttpProject()
    project.addEnvironment('e1')
    f1 = project.addFolder('f1')
    e2 = f1.addEnvironment('e2')
  })

  test('removes the environment from folder items', ({ assert }) => {
    f1.removeEnvironment(e2.key)
    assert.deepEqual(f1.items, [])
  })

  test('does nothing when the environment is not in the folder', ({ assert }) => {
    const f2 = f1.addFolder('f2') // e2 is in f1, f2 is a child of f1
    const e3 = f2.addEnvironment('e3') // e3 is in f2
    const result = f1.removeEnvironment(e3.key) // Trying to remove e3 from f1
    assert.isUndefined(result)
    assert.lengthOf(f1.items, 2) // f1 should still have e2 and f2
  })

  test('returns the removed environment', ({ assert }) => {
    const result = f1.removeEnvironment(e2.key)
    assert.deepEqual(result, e2)
  })
})
