import { test } from '@japa/runner'
import { Invitation, InvitationKind, type InvitationSchema, type IDeletion } from '../../../../src/index.js'
import { OperationType } from '@api-client/json/patch/types.js'

const baseInput: Partial<InvitationSchema> = {
  email: 'test@example.com',
  oid: 'org-123',
  grantType: 'viewer',
}

test.group('Invitation', (group) => {
  group.tap((t) => t.tags(['@store', '@invitation']))

  test('constructor initializes with minimal valid input', ({ assert }) => {
    const invitation = new Invitation(baseInput)
    assert.equal(invitation.kind, InvitationKind)
    assert.isString(invitation.key)
    assert.isNotEmpty(invitation.key)
    assert.equal(invitation.email, baseInput.email)
    assert.equal(invitation.oid, baseInput.oid)
    assert.equal(invitation.grantType, baseInput.grantType)
    assert.equal(invitation.uid, '') // default
    assert.equal(invitation.token, '') // default
    assert.equal(invitation.expiresAt, 0) // default
    assert.equal(invitation.status, 'pending') // default
    assert.isUndefined(invitation.name)
    assert.isUndefined(invitation.respondedAt)
    assert.isFalse(invitation.deleted)
    assert.isUndefined(invitation.deletedInfo)
    assert.equal(invitation.createdAt, 0) // default
    assert.equal(invitation.updatedAt, 0) // default
  })

  test('constructor initializes with full input', ({ assert }) => {
    const fullInput: InvitationSchema = {
      kind: InvitationKind,
      key: 'inv-key-123',
      uid: 'user-abc',
      oid: 'org-xyz',
      email: 'full@example.com',
      name: 'Test User',
      token: 'secret-token',
      expiresAt: Date.now() + 3600000,
      respondedAt: Date.now(),
      status: 'accepted',
      grantType: 'editor',
      deleted: true,
      deletedInfo: { time: Date.now(), user: 'admin-user', byMe: false, name: 'Admin' },
      createdAt: Date.now() - 7200000,
      updatedAt: Date.now() - 3600000,
      resent: 0,
      lastSentAt: Date.now() - 180000,
    }
    const invitation = new Invitation(fullInput)
    assert.deepEqual(invitation.toJSON(), fullInput)
  })

  test('toJSON() serializes all properties correctly', ({ assert }) => {
    const deletionInfo: IDeletion = { time: Date.now(), user: 'deleter-id', byMe: false, name: 'Deleter' }
    const input: Partial<InvitationSchema> = {
      ...baseInput,
      key: 'inv-key-json',
      uid: 'user-json',
      name: 'JSON User',
      token: 'json-token',
      expiresAt: 1234567890,
      respondedAt: 1234500000,
      status: 'declined',
      grantType: 'manager',
      deleted: true,
      deletedInfo: deletionInfo,
      createdAt: 1230000000,
      updatedAt: 1234000000,
    }
    const invitation = new Invitation(input)
    const json = invitation.toJSON()

    assert.deepEqual(json, {
      kind: InvitationKind,
      key: 'inv-key-json',
      uid: 'user-json',
      oid: baseInput.oid,
      email: baseInput.email,
      name: 'JSON User',
      token: 'json-token',
      expiresAt: 1234567890,
      respondedAt: 1234500000,
      status: 'declined',
      grantType: 'manager',
      deleted: true,
      deletedInfo: deletionInfo,
      createdAt: 1230000000,
      updatedAt: 1234000000,
      resent: 0, // default
      lastSentAt: 0, // default
    })
  })

  test('toJSON() omits optional fields when not set', ({ assert }) => {
    const invitation = new Invitation(baseInput)
    const json = invitation.toJSON()

    assert.notProperty(json, 'name')
    assert.notProperty(json, 'respondedAt')
    assert.notProperty(json, 'deletedInfo') // deleted is false, so deletedInfo should not be present
  })

  test('getPatch() returns empty array when no changes', ({ assert }) => {
    const invitation = new Invitation(baseInput)
    assert.deepEqual(invitation.getPatch(), [])
  })

  test('setName() adds name if not present', ({ assert }) => {
    const invitation = new Invitation(baseInput)
    invitation.setName('New Name')
    assert.deepEqual(invitation.getPatch(), [{ op: OperationType.add, path: '/name', value: 'New Name' }])
  })

  test('setName() replaces name if different', ({ assert }) => {
    const invitation = new Invitation({ ...baseInput, name: 'Old Name' })
    invitation.setName('New Name')
    assert.deepEqual(invitation.getPatch(), [{ op: OperationType.replace, path: '/name', value: 'New Name' }])
  })

  test('setName() removes name if set to undefined', ({ assert }) => {
    const invitation = new Invitation({ ...baseInput, name: 'Old Name' })
    invitation.setName(undefined)
    assert.deepEqual(invitation.getPatch(), [{ op: OperationType.remove, path: '/name' }])
  })

  test('setName() does nothing if name is the same', ({ assert }) => {
    const invitation = new Invitation({ ...baseInput, name: 'Same Name' })
    invitation.setName('Same Name')
    assert.deepEqual(invitation.getPatch(), [])
  })

  test('setExpiresAt() replaces expiresAt if different', ({ assert }) => {
    const invitation = new Invitation({ ...baseInput, expiresAt: 1000 })
    invitation.setExpiresAt(2000)
    assert.deepEqual(invitation.getPatch(), [{ op: OperationType.replace, path: '/expiresAt', value: 2000 }])
  })

  test('setExpiresAt() does nothing if expiresAt is the same', ({ assert }) => {
    const invitation = new Invitation({ ...baseInput, expiresAt: 1000 })
    invitation.setExpiresAt(1000)
    assert.deepEqual(invitation.getPatch(), [])
  })

  test('setGrantType() replaces grantType if different', ({ assert }) => {
    const invitation = new Invitation({ ...baseInput, grantType: 'viewer' })
    invitation.setGrantType('editor')
    assert.deepEqual(invitation.getPatch(), [{ op: OperationType.replace, path: '/grantType', value: 'editor' }])
  })

  test('setGrantType() does nothing if grantType is the same', ({ assert }) => {
    const invitation = new Invitation({ ...baseInput, grantType: 'viewer' })
    invitation.setGrantType('viewer')
    assert.deepEqual(invitation.getPatch(), [])
  })

  test('builder getter initializes and returns builder', ({ assert }) => {
    const invitation = new Invitation(baseInput)
    const builder1 = invitation.builder
    assert.isObject(builder1)
    const builder2 = invitation.builder
    assert.strictEqual(builder1, builder2) // Should return the same instance
  })
})

test.group('Invitation.createSchema()', (schemaGroup) => {
  schemaGroup.tap((t) => t.tags(['@store', '@invitation', '@static']))

  test('throws if email is missing', ({ assert }) => {
    assert.throws(
      () => Invitation.createSchema({ oid: 'org-1', grantType: 'viewer' }),
      'Email is required to create an invitation schema.'
    )
  })

  test('throws if oid is missing', ({ assert }) => {
    assert.throws(
      () => Invitation.createSchema({ email: 'test@example.com', grantType: 'viewer' }),
      'Organization ID is required to create an invitation schema.'
    )
  })

  test('throws if grantType is missing', ({ assert }) => {
    assert.throws(
      () => Invitation.createSchema({ email: 'test@example.com', oid: 'org-1' }),
      'Grant type is required to create an invitation schema.'
    )
  })

  test('creates schema with all default values for optional fields', ({ assert }) => {
    const schema = Invitation.createSchema(baseInput)
    assert.equal(schema.kind, InvitationKind)
    assert.isString(schema.key)
    assert.equal(schema.uid, '')
    assert.equal(schema.oid, baseInput.oid)
    assert.equal(schema.email, baseInput.email)
    assert.equal(schema.grantType, baseInput.grantType)
    assert.equal(schema.token, '')
    assert.equal(schema.expiresAt, 0)
    assert.equal(schema.status, 'pending')
    assert.equal(schema.createdAt, 0)
    assert.equal(schema.updatedAt, 0)
    assert.isUndefined(schema.name)
    assert.isUndefined(schema.respondedAt)
  })

  test('creates schema with provided optional values', ({ assert }) => {
    const partial: Partial<InvitationSchema> = {
      ...baseInput,
      name: 'Provided Name',
      token: 'provided-token',
      expiresAt: 999,
      respondedAt: 888,
      status: 'accepted',
    }
    const schema = Invitation.createSchema(partial)
    assert.equal(schema.name, 'Provided Name')
    assert.equal(schema.token, 'provided-token')
    assert.equal(schema.expiresAt, 999)
    assert.equal(schema.respondedAt, 888)
    assert.equal(schema.status, 'accepted')
  })
})
