import { test } from '@japa/runner'
import sinon from 'sinon'
import { DataCatalog } from '../../../../src/mocking/lib/DataCatalog.js'
import { DataCatalogKind, DataCatalogVersionKind } from '../../../../src/models/kinds.js'
import type {
  DataCatalogSchema,
  DataCatalogSchemaWithVersion,
  DataCatalogVersionInfo,
} from '../../../../src/models/DataCatalog.js'
import type { DataCatalogVersionSchema } from '../../../../src/models/DataCatalogVersion.js'

test.group('dataCatalog()', (group) => {
  let catalog: DataCatalog

  group.each.setup(() => {
    catalog = new DataCatalog()
  })

  test('returns an object', ({ assert }) => {
    const result = catalog.dataCatalog()
    assert.typeOf(result, 'object')
  })

  test('has the {property} property of a type {type}')
    .with([
      { property: 'kind', type: 'string' },
      { property: 'key', type: 'string' },
      { property: 'name', type: 'string' },
      { property: 'organization', type: 'string' },
      { property: 'file', type: 'string' },
      { property: 'scope', type: 'string' },
      { property: 'description', type: 'string' },
      { property: 'publishedBy', type: 'string' },
      { property: 'publishedAt', type: 'number' },
      { property: 'createdAt', type: 'number' },
      { property: 'updatedAt', type: 'number' },
      { property: 'tags', type: 'array' },
    ])
    .run(({ assert }, { property, type }) => {
      const result = catalog.dataCatalog()
      assert.typeOf(result[property as keyof DataCatalogSchema], type)
    })

  test('has the correct kind', ({ assert }) => {
    const result = catalog.dataCatalog()
    assert.equal(result.kind, DataCatalogKind)
  })

  test('scope is one of the valid values', ({ assert }) => {
    const result = catalog.dataCatalog()
    assert.include(['public', 'private', 'organization'], result.scope)
  })

  test('uses passed key', ({ assert }) => {
    const result = catalog.dataCatalog({ key: 'custom-key' })
    assert.equal(result.key, 'custom-key')
  })

  test('uses passed name', ({ assert }) => {
    const result = catalog.dataCatalog({ name: 'Custom Catalog' })
    assert.equal(result.name, 'Custom Catalog')
  })

  test('uses passed organization', ({ assert }) => {
    const result = catalog.dataCatalog({ organization: 'org-123' })
    assert.equal(result.organization, 'org-123')
  })

  test('uses passed file', ({ assert }) => {
    const result = catalog.dataCatalog({ file: 'file-456' })
    assert.equal(result.file, 'file-456')
  })

  test('uses passed scope', ({ assert }) => {
    const result = catalog.dataCatalog({ scope: 'private' })
    assert.equal(result.scope, 'private')
  })

  test('uses passed description', ({ assert }) => {
    const result = catalog.dataCatalog({ description: 'Test description' })
    assert.equal(result.description, 'Test description')
  })

  test('uses passed publishedBy', ({ assert }) => {
    const result = catalog.dataCatalog({ publishedBy: 'user-789' })
    assert.equal(result.publishedBy, 'user-789')
  })

  test('uses passed publishedAt', ({ assert }) => {
    const timestamp = Date.now()
    const result = catalog.dataCatalog({ publishedAt: timestamp })
    assert.equal(result.publishedAt, timestamp)
  })

  test('uses passed createdAt', ({ assert }) => {
    const timestamp = Date.now()
    const result = catalog.dataCatalog({ createdAt: timestamp })
    assert.equal(result.createdAt, timestamp)
  })

  test('uses passed updatedAt', ({ assert }) => {
    const timestamp = Date.now()
    const result = catalog.dataCatalog({ updatedAt: timestamp })
    assert.equal(result.updatedAt, timestamp)
  })

  test('uses passed tags', ({ assert }) => {
    const tags = ['tag1', 'tag2', 'tag3']
    const result = catalog.dataCatalog({ tags })
    assert.deepEqual(result.tags, tags)
  })
})

test.group('dataCatalogs()', (group) => {
  let catalog: DataCatalog

  group.each.setup(() => {
    catalog = new DataCatalog()
  })

  test('returns an array', ({ assert }) => {
    const result = catalog.dataCatalogs()
    assert.typeOf(result, 'array')
  })

  test('list has default number of items', ({ assert }) => {
    const result = catalog.dataCatalogs()
    assert.lengthOf(result, 25)
  })

  test('returns requested number of items', ({ assert }) => {
    const result = catalog.dataCatalogs(10)
    assert.lengthOf(result, 10)
  })

  test('calls dataCatalog()', ({ assert }) => {
    const spy = sinon.spy(catalog, 'dataCatalog')
    catalog.dataCatalogs(5)
    assert.equal(spy.callCount, 5)
  })
})

test.group('dataCatalogVersion()', (group) => {
  let catalog: DataCatalog

  group.each.setup(() => {
    catalog = new DataCatalog()
  })

  test('returns an object', ({ assert }) => {
    const result = catalog.dataCatalogVersion()
    assert.typeOf(result, 'object')
  })

  test('has the {property} property of a type {type}')
    .with([
      { property: 'kind', type: 'string' },
      { property: 'key', type: 'string' },
      { property: 'catalogKey', type: 'string' },
      { property: 'scope', type: 'string' },
      { property: 'lifecycle', type: 'string' },
      { property: 'version', type: 'string' },
      { property: 'publishedBy', type: 'string' },
      { property: 'createdAt', type: 'number' },
      { property: 'updatedAt', type: 'number' },
      { property: 'changelog', type: 'string' },
    ])
    .run(({ assert }, { property, type }) => {
      const result = catalog.dataCatalogVersion()
      assert.typeOf(result[property as keyof DataCatalogVersionSchema], type)
    })

  test('has the correct kind', ({ assert }) => {
    const result = catalog.dataCatalogVersion()
    assert.equal(result.kind, DataCatalogVersionKind)
  })

  test('scope is one of the valid values', ({ assert }) => {
    const result = catalog.dataCatalogVersion()
    assert.include(['public', 'private', 'organization'], result.scope)
  })

  test('lifecycle is one of the valid values', ({ assert }) => {
    const result = catalog.dataCatalogVersion()
    assert.include(['stable', 'beta', 'dev'], result.lifecycle)
  })

  test('version starts with v', ({ assert }) => {
    const result = catalog.dataCatalogVersion()
    assert.isTrue(result.version.startsWith('v'))
  })

  test('uses passed key', ({ assert }) => {
    const result = catalog.dataCatalogVersion({ key: 'version-key' })
    assert.equal(result.key, 'version-key')
  })

  test('uses passed catalogKey', ({ assert }) => {
    const result = catalog.dataCatalogVersion({ catalogKey: 'catalog-123' })
    assert.equal(result.catalogKey, 'catalog-123')
  })

  test('uses passed scope', ({ assert }) => {
    const result = catalog.dataCatalogVersion({ scope: 'private' })
    assert.equal(result.scope, 'private')
  })

  test('uses passed lifecycle', ({ assert }) => {
    const result = catalog.dataCatalogVersion({ lifecycle: 'beta' })
    assert.equal(result.lifecycle, 'beta')
  })

  test('uses passed version', ({ assert }) => {
    const result = catalog.dataCatalogVersion({ version: 'v2.0.0' })
    assert.equal(result.version, 'v2.0.0')
  })

  test('uses passed publishedBy', ({ assert }) => {
    const result = catalog.dataCatalogVersion({ publishedBy: 'user-999' })
    assert.equal(result.publishedBy, 'user-999')
  })

  test('uses passed createdAt', ({ assert }) => {
    const timestamp = Date.now()
    const result = catalog.dataCatalogVersion({ createdAt: timestamp })
    assert.equal(result.createdAt, timestamp)
  })

  test('uses passed updatedAt', ({ assert }) => {
    const timestamp = Date.now()
    const result = catalog.dataCatalogVersion({ updatedAt: timestamp })
    assert.equal(result.updatedAt, timestamp)
  })

  test('uses passed changelog', ({ assert }) => {
    const result = catalog.dataCatalogVersion({ changelog: 'Custom changelog' })
    assert.equal(result.changelog, 'Custom changelog')
  })
})

test.group('dataCatalogVersions()', (group) => {
  let catalog: DataCatalog

  group.each.setup(() => {
    catalog = new DataCatalog()
  })

  test('returns an array', ({ assert }) => {
    const result = catalog.dataCatalogVersions()
    assert.typeOf(result, 'array')
  })

  test('list has default number of items', ({ assert }) => {
    const result = catalog.dataCatalogVersions()
    assert.lengthOf(result, 25)
  })

  test('returns requested number of items', ({ assert }) => {
    const result = catalog.dataCatalogVersions(7)
    assert.lengthOf(result, 7)
  })

  test('calls dataCatalogVersion()', ({ assert }) => {
    const spy = sinon.spy(catalog, 'dataCatalogVersion')
    catalog.dataCatalogVersions(3)
    assert.equal(spy.callCount, 3)
  })
})

test.group('dataCatalogWithVersion()', (group) => {
  let catalog: DataCatalog

  group.each.setup(() => {
    catalog = new DataCatalog()
  })

  test('returns an object', ({ assert }) => {
    const result = catalog.dataCatalogWithVersion()
    assert.typeOf(result, 'object')
  })

  test('has the {property} property of a type {type}')
    .with([
      { property: 'kind', type: 'string' },
      { property: 'key', type: 'string' },
      { property: 'name', type: 'string' },
      { property: 'organization', type: 'string' },
      { property: 'file', type: 'string' },
      { property: 'scope', type: 'string' },
      { property: 'description', type: 'string' },
      { property: 'publishedBy', type: 'string' },
      { property: 'publishedAt', type: 'number' },
      { property: 'createdAt', type: 'number' },
      { property: 'updatedAt', type: 'number' },
      { property: 'tags', type: 'array' },
      { property: 'versions', type: 'array' },
    ])
    .run(({ assert }, { property, type }) => {
      const result = catalog.dataCatalogWithVersion()
      assert.typeOf(result[property as keyof DataCatalogSchemaWithVersion], type)
    })

  test('has at least one version', ({ assert }) => {
    const result = catalog.dataCatalogWithVersion()
    assert.isAtLeast(result.versions.length, 1)
  })

  test('has at most 20 versions', ({ assert }) => {
    const result = catalog.dataCatalogWithVersion()
    assert.isAtMost(result.versions.length, 20)
  })

  test('each version has required properties', ({ assert }) => {
    const result = catalog.dataCatalogWithVersion()
    result.versions.forEach((version) => {
      assert.typeOf(version.version, 'string')
      assert.typeOf(version.lifecycle, 'string')
      assert.include(['stable', 'beta', 'dev'], version.lifecycle)
    })
  })

  test('versions follow semantic versioning pattern', ({ assert }) => {
    const result = catalog.dataCatalogWithVersion()
    result.versions.forEach((version) => {
      assert.match(version.version, /^v\d+\.\d+\.\d+$/)
    })
  })

  test('uses passed initialization values', ({ assert }) => {
    const result = catalog.dataCatalogWithVersion({
      key: 'custom-key',
      name: 'Custom Name',
    })
    assert.equal(result.key, 'custom-key')
    assert.equal(result.name, 'Custom Name')
    assert.isArray(result.versions)
  })
})

test.group('dataCatalogsWithVersion()', (group) => {
  let catalog: DataCatalog

  group.each.setup(() => {
    catalog = new DataCatalog()
  })

  test('returns an array', ({ assert }) => {
    const result = catalog.dataCatalogsWithVersion()
    assert.typeOf(result, 'array')
  })

  test('list has default number of items', ({ assert }) => {
    const result = catalog.dataCatalogsWithVersion()
    assert.lengthOf(result, 3)
  })

  test('returns requested number of items', ({ assert }) => {
    const result = catalog.dataCatalogsWithVersion(5)
    assert.lengthOf(result, 5)
  })

  test('calls dataCatalogWithVersion()', ({ assert }) => {
    const spy = sinon.spy(catalog, 'dataCatalogWithVersion')
    catalog.dataCatalogsWithVersion(4)
    assert.equal(spy.callCount, 4)
  })

  test('each item has versions array', ({ assert }) => {
    const result = catalog.dataCatalogsWithVersion(2)
    result.forEach((item) => {
      assert.isArray(item.versions)
      assert.isAtLeast(item.versions.length, 1)
    })
  })
})

test.group('versionInfo()', (group) => {
  let catalog: DataCatalog

  group.each.setup(() => {
    catalog = new DataCatalog()
  })

  test('returns an object', ({ assert }) => {
    const result = catalog.versionInfo()
    assert.typeOf(result, 'object')
  })

  test('has the {property} property of a type {type}')
    .with([
      { property: 'key', type: 'string' },
      { property: 'version', type: 'string' },
      { property: 'published', type: 'number' },
    ])
    .run(({ assert }, { property, type }) => {
      const result = catalog.versionInfo()
      assert.typeOf(result[property as keyof DataCatalogVersionInfo], type)
    })

  test('version starts with v', ({ assert }) => {
    const result = catalog.versionInfo()
    assert.isTrue(result.version.startsWith('v'))
  })

  test('uses passed key', ({ assert }) => {
    const result = catalog.versionInfo({ key: 'info-key' })
    assert.equal(result.key, 'info-key')
  })

  test('uses passed version', ({ assert }) => {
    const result = catalog.versionInfo({ version: 'v3.0.0' })
    assert.equal(result.version, 'v3.0.0')
  })

  test('uses passed published timestamp', ({ assert }) => {
    const timestamp = Date.now()
    const result = catalog.versionInfo({ published: timestamp })
    assert.equal(result.published, timestamp)
  })
})

test.group('versionInfos()', (group) => {
  let catalog: DataCatalog

  group.each.setup(() => {
    catalog = new DataCatalog()
  })

  test('returns an array', ({ assert }) => {
    const result = catalog.versionInfos()
    assert.typeOf(result, 'array')
  })

  test('list has default number of items', ({ assert }) => {
    const result = catalog.versionInfos()
    assert.lengthOf(result, 5)
  })

  test('returns requested number of items', ({ assert }) => {
    const result = catalog.versionInfos(8)
    assert.lengthOf(result, 8)
  })

  test('calls versionInfo()', ({ assert }) => {
    const spy = sinon.spy(catalog, 'versionInfo')
    catalog.versionInfos(6)
    assert.equal(spy.callCount, 6)
  })
})
