import { test } from '@japa/runner'
import { ApiModel, ExposedEntity, type ExposedEntitySchema } from '../../../src/index.js'
import { ExposedEntityKind } from '../../../src/models/kinds.js'

test.group('ExposedEntity', () => {
  test('setCollectionPath normalizes and preserves resource param', ({ assert }) => {
    const model = new ApiModel()
    const ex = new ExposedEntity(model, {
      hasCollection: true,
      collectionPath: '/items',
      resourcePath: '/items/{customId}',
    })

    ex.setCollectionPath('products')

    assert.equal(ex.collectionPath, '/products')
    assert.equal(ex.resourcePath, '/products/{customId}')
  }).tags(['@modeling', '@exposed-entity'])

  test('setResourcePath with collection allows only parameter name change', ({ assert }) => {
    const model = new ApiModel()
    const ex = new ExposedEntity(model, {
      hasCollection: true,
      collectionPath: '/products',
      resourcePath: '/products/{id}',
    })

    // valid: same collection segment, different param name
    ex.setResourcePath('/products/{productId}')
    assert.equal(ex.resourcePath, '/products/{productId}')

    // invalid: different first segment
    assert.throws(() => ex.setResourcePath('/wrong/{id}'))

    // invalid: second segment not a parameter
    assert.throws(() => ex.setResourcePath('/products/notParam'))
  }).tags(['@modeling', '@exposed-entity'])

  test('setResourcePath without collection must have exactly two segments', ({ assert }) => {
    const model = new ApiModel()
    const ex = new ExposedEntity(model, {
      hasCollection: false,
      resourcePath: '/profile/{id}',
    })

    ex.setResourcePath('settings/secret')
    assert.equal(ex.resourcePath, '/settings/secret')

    assert.throws(() => ex.setResourcePath('onlyone'))
  }).tags(['@modeling', '@exposed-entity'])

  test('computes absolute resource and collection paths along parent chain', ({ assert }) => {
    const model = new ApiModel()

    // Build exposure schemas
    const rootSchema: Partial<ExposedEntitySchema> = {
      key: 'root',
      entity: { key: 'user' },
      hasCollection: true,
      collectionPath: '/users',
      resourcePath: '/users/{userId}',
      isRoot: true,
      actions: [],
    }
    const childSchema: Partial<ExposedEntitySchema> = {
      key: 'child',
      entity: { key: 'post' },
      hasCollection: true,
      collectionPath: '/posts',
      resourcePath: '/posts/{postId}',
      parent: { key: 'root', association: { key: 'toPosts' } },
      actions: [],
    }
    const grandSchema: Partial<ExposedEntitySchema> = {
      key: 'grand',
      entity: { key: 'details' },
      hasCollection: false,
      resourcePath: '/details',
      parent: { key: 'child', association: { key: 'toDetails' } },
      actions: [],
    }
    // Instantiate instances bound to the model
    const rootEx = new ExposedEntity(model, rootSchema)
    const childEx = new ExposedEntity(model, childSchema)
    const grandEx = new ExposedEntity(model, grandSchema)
    // attach to model as instances
    model.exposes = [rootEx, childEx, grandEx]

    // root
    assert.equal(rootEx.getAbsoluteCollectionPath(), '/users')
    assert.equal(rootEx.getAbsoluteResourcePath(), '/users/{userId}')

    // child
    assert.equal(childEx.getAbsoluteCollectionPath(), '/users/{userId}/posts')
    assert.equal(childEx.getAbsoluteResourcePath(), '/users/{userId}/posts/{postId}')

    // grand (no collection)
    assert.isUndefined(grandEx.getAbsoluteCollectionPath())
    assert.equal(grandEx.getAbsoluteResourcePath(), '/users/{userId}/posts/{postId}/details')
  }).tags(['@modeling', '@exposed-entity'])

  test('ApiModel notifies when nested ExposedEntity collection path changes', async ({ assert }) => {
    const model = new ApiModel({
      exposes: [
        {
          kind: ExposedEntityKind,
          key: 'e1',
          entity: { key: 'e1' },
          hasCollection: true,
          collectionPath: '/things',
          resourcePath: '/things/{id}',
          isRoot: true,
          actions: [],
        },
      ],
    })

    let notified = 0
    model.addEventListener('change', () => {
      notified += 1
    })

    const ex = model.exposes[0]
    ex.setCollectionPath('items')
    await Promise.resolve() // allow ApiModel.notifyChange microtask to run
    assert.isAtLeast(notified, 1)
  }).tags(['@modeling', '@exposed-entity', '@observed'])

  test('ApiModel notifies when nested ExposedEntity resource path changes', async ({ assert }) => {
    const model = new ApiModel({
      exposes: [
        {
          kind: ExposedEntityKind,
          key: 'e1',
          entity: { key: 'e1' },
          hasCollection: true,
          collectionPath: '/products',
          resourcePath: '/products/{id}',
          isRoot: true,
          actions: [],
        },
      ],
    })

    let notified = 0
    model.addEventListener('change', () => {
      notified += 1
    })

    const ex = model.exposes[0]
    ex.setResourcePath('/products/{productId}')
    await Promise.resolve()
    assert.isAtLeast(notified, 1)
  }).tags(['@modeling', '@exposed-entity', '@observed'])
})
