import { test } from '@japa/runner'
import { Property, IProperty, Kind as PropertyKind, PropertyType } from '../../../src/models/Property.js'

test.group('Models > Property > Property.fromType()', () => {
  test('creates a type from a boolean value', ({ assert }) => {
    const result = Property.fromType('a', false)
    assert.equal(result.name, 'a')
    assert.equal(result.value, false)
    assert.equal(result.type, 'boolean')
    assert.equal(result.enabled, true)
  })

  test('creates a type from a boolean value with the enabled flag', ({ assert }) => {
    const result = Property.fromType('a', false, false)
    assert.equal(result.name, 'a')
    assert.equal(result.type, 'boolean')
    assert.equal(result.enabled, false)
  })

  test('creates a type from an integer value', ({ assert }) => {
    const result = Property.fromType('a', 1)
    assert.equal(result.name, 'a')
    assert.equal(result.value, 1)
    assert.equal(result.type, 'integer')
    assert.equal(result.enabled, true)
  })

  test('creates a type from an integer value with the enabled flag', ({ assert }) => {
    const result = Property.fromType('a', 2, false)
    assert.equal(result.name, 'a')
    assert.equal(result.value, 2)
    assert.equal(result.type, 'integer')
    assert.equal(result.enabled, false)
  })

  test('creates a type from a string value', ({ assert }) => {
    const result = Property.fromType('a', 'b')
    assert.equal(result.name, 'a')
    assert.equal(result.value, 'b')
    assert.equal(result.type, 'string')
    assert.equal(result.enabled, true)
  })

  test('creates a type from a string value with the enabled flag', ({ assert }) => {
    const result = Property.fromType('a', 'b', false)
    assert.equal(result.name, 'a')
    assert.equal(result.value, 'b')
    assert.equal(result.type, 'string')
    assert.equal(result.enabled, false)
  })
})

test.group('Models > Property > Property.String()', () => {
  test('creates a string property with defaults', ({ assert }) => {
    const result = Property.String('a')
    assert.equal(result.kind, PropertyKind)
    assert.equal(result.name, 'a')
    assert.equal(result.value, '')
    assert.equal(result.enabled, true)
    assert.equal(result.type, 'string')
  })

  test('creates a string property with a value', ({ assert }) => {
    const result = Property.String('a', 'b')
    assert.equal(result.value, 'b')
  })

  test('creates a string property with the enabled flag', ({ assert }) => {
    const result = Property.String('a', 'b', false)
    assert.equal(result.enabled, false)
  })
})

test.group('Models > Property > Property.Integer()', () => {
  test('creates an integer property with defaults', ({ assert }) => {
    const result = Property.Integer('a')
    assert.equal(result.kind, PropertyKind)
    assert.equal(result.name, 'a')
    assert.equal(result.value, 0)
    assert.equal(result.enabled, true)
    assert.equal(result.type, 'integer')
  })

  test('creates an integer property with a value', ({ assert }) => {
    const result = Property.Integer('a', 1)
    assert.equal(result.value, 1)
  })

  test('creates an integer property with the enabled flag', ({ assert }) => {
    const result = Property.Integer('a', 1, false)
    assert.equal(result.enabled, false)
  })
})

test.group('Models > Property > Property.Int32()', () => {
  test('creates an int32 property with defaults', ({ assert }) => {
    const result = Property.Int32('a')
    assert.equal(result.kind, PropertyKind)
    assert.equal(result.name, 'a')
    assert.equal(result.value, 0)
    assert.equal(result.enabled, true)
    assert.equal(result.type, 'int32')
  })

  test('creates an int32 property with a value', ({ assert }) => {
    const result = Property.Int32('a', 1)
    assert.equal(result.value, 1)
  })

  test('creates an int32 property with the enabled flag', ({ assert }) => {
    const result = Property.Int32('a', 1, false)
    assert.equal(result.enabled, false)
  })
})

test.group('Models > Property > Property.Int64()', () => {
  test('creates an int64 property with defaults', ({ assert }) => {
    const result = Property.Int64('a')
    assert.equal(result.kind, PropertyKind)
    assert.equal(result.name, 'a')
    assert.equal(result.value, 0)
    assert.equal(result.enabled, true)
    assert.equal(result.type, 'int64')
  })

  test('creates an int64 property with a value', ({ assert }) => {
    const result = Property.Int64('a', 1)
    assert.equal(result.value, 1)
  })

  test('creates an int64 property with the enabled flag', ({ assert }) => {
    const result = Property.Int64('a', 1, false)
    assert.equal(result.enabled, false)
  })
})

test.group('Models > Property > Property.Uint32()', () => {
  test('creates an uint32 property with defaults', ({ assert }) => {
    const result = Property.Uint32('a')
    assert.equal(result.kind, PropertyKind)
    assert.equal(result.name, 'a')
    assert.equal(result.value, 0)
    assert.equal(result.enabled, true)
    assert.equal(result.type, 'uint32')
  })

  test('creates an uint32 property with a value', ({ assert }) => {
    const result = Property.Uint32('a', 1)
    assert.equal(result.value, 1)
  })

  test('creates an uint32 property with the enabled flag', ({ assert }) => {
    const result = Property.Uint32('a', 1, false)
    assert.equal(result.enabled, false)
  })
})

test.group('Models > Property > Property.Uint64()', () => {
  test('creates an uint64 property with defaults', ({ assert }) => {
    const result = Property.Uint64('a')
    assert.equal(result.kind, PropertyKind)
    assert.equal(result.name, 'a')
    assert.equal(result.value, 0)
    assert.equal(result.enabled, true)
    assert.equal(result.type, 'uint64')
  })

  test('creates an uint64 property with a value', ({ assert }) => {
    const result = Property.Uint64('a', 1)
    assert.equal(result.value, 1)
  })

  test('creates an uint64 property with the enabled flag', ({ assert }) => {
    const result = Property.Uint64('a', 1, false)
    assert.equal(result.enabled, false)
  })
})

test.group('Models > Property > Property.Sint32()', () => {
  test('creates an sint32 property with defaults', ({ assert }) => {
    const result = Property.Sint32('a')
    assert.equal(result.kind, PropertyKind)
    assert.equal(result.name, 'a')
    assert.equal(result.value, 0)
    assert.equal(result.enabled, true)
    assert.equal(result.type, 'sint32')
  })

  test('creates an sint32 property with a value', ({ assert }) => {
    const result = Property.Sint32('a', 1)
    assert.equal(result.value, 1)
  })

  test('creates an sint32 property with the enabled flag', ({ assert }) => {
    const result = Property.Sint32('a', 1, false)
    assert.equal(result.enabled, false)
  })
})

test.group('Models > Property > Property.Sint64()', () => {
  test('creates an sint64 property with defaults', ({ assert }) => {
    const result = Property.Sint64('a')
    assert.equal(result.kind, PropertyKind)
    assert.equal(result.name, 'a')
    assert.equal(result.value, 0)
    assert.equal(result.enabled, true)
    assert.equal(result.type, 'sint64')
  })

  test('creates an sint64 property with a value', ({ assert }) => {
    const result = Property.Sint64('a', 1)
    assert.equal(result.value, 1)
  })

  test('creates an sint64 property with the enabled flag', ({ assert }) => {
    const result = Property.Sint64('a', 1, false)
    assert.equal(result.enabled, false)
  })
})

test.group('Models > Property > Property.Fixed32()', () => {
  test('creates an fixed32 property with defaults', ({ assert }) => {
    const result = Property.Fixed32('a')
    assert.equal(result.kind, PropertyKind)
    assert.equal(result.name, 'a')
    assert.equal(result.value, 0)
    assert.equal(result.enabled, true)
    assert.equal(result.type, 'fixed32')
  })

  test('creates an fixed32 property with a value', ({ assert }) => {
    const result = Property.Fixed32('a', 1)
    assert.equal(result.value, 1)
  })

  test('creates an fixed32 property with the enabled flag', ({ assert }) => {
    const result = Property.Fixed32('a', 1, false)
    assert.equal(result.enabled, false)
  })
})

test.group('Models > Property > Property.Fixed64()', () => {
  test('creates an fixed64 property with defaults', ({ assert }) => {
    const result = Property.Fixed64('a')
    assert.equal(result.kind, PropertyKind)
    assert.equal(result.name, 'a')
    assert.equal(result.value, 0)
    assert.equal(result.enabled, true)
    assert.equal(result.type, 'fixed64')
  })

  test('creates an fixed64 property with a value', ({ assert }) => {
    const result = Property.Fixed64('a', 1)
    assert.equal(result.value, 1)
  })

  test('creates an fixed64 property with the enabled flag', ({ assert }) => {
    const result = Property.Fixed64('a', 1, false)
    assert.equal(result.enabled, false)
  })
})

test.group('Models > Property > Property.Sfixed32()', () => {
  test('creates an sfixed32 property with defaults', ({ assert }) => {
    const result = Property.Sfixed32('a')
    assert.equal(result.kind, PropertyKind)
    assert.equal(result.name, 'a')
    assert.equal(result.value, 0)
    assert.equal(result.enabled, true)
    assert.equal(result.type, 'sfixed32')
  })

  test('creates an sfixed32 property with a value', ({ assert }) => {
    const result = Property.Sfixed32('a', 1)
    assert.equal(result.value, 1)
  })

  test('creates an sfixed32 property with the enabled flag', ({ assert }) => {
    const result = Property.Sfixed32('a', 1, false)
    assert.equal(result.enabled, false)
  })
})

test.group('Models > Property > Property.Sfixed64()', () => {
  test('creates an sfixed64 property with defaults', ({ assert }) => {
    const result = Property.Sfixed64('a')
    assert.equal(result.kind, PropertyKind)
    assert.equal(result.name, 'a')
    assert.equal(result.value, 0)
    assert.equal(result.enabled, true)
    assert.equal(result.type, 'sfixed64')
  })

  test('creates an sfixed64 property with a value', ({ assert }) => {
    const result = Property.Sfixed64('a', 1)
    assert.equal(result.value, 1)
  })

  test('creates an sfixed64 property with the enabled flag', ({ assert }) => {
    const result = Property.Sfixed64('a', 1, false)
    assert.equal(result.enabled, false)
  })
})

test.group('Models > Property > Property.Float()', () => {
  test('creates a float property with defaults', ({ assert }) => {
    const result = Property.Float('a')
    assert.equal(result.kind, PropertyKind)
    assert.equal(result.name, 'a')
    assert.equal(result.value, 0.0)
    assert.equal(result.enabled, true)
    assert.equal(result.type, 'float')
  })

  test('creates a float property with a value', ({ assert }) => {
    const result = Property.Float('a', 1.3)
    assert.equal(result.value, 1.3)
  })

  test('creates a float property with the enabled flag', ({ assert }) => {
    const result = Property.Float('a', 1.3, false)
    assert.equal(result.enabled, false)
  })
})

test.group('Models > Property > Property.Boolean()', () => {
  test('creates a boolean property with defaults', ({ assert }) => {
    const result = Property.Boolean('a')
    assert.equal(result.kind, PropertyKind)
    assert.equal(result.name, 'a')
    assert.equal(result.value, false)
    assert.equal(result.enabled, true)
    assert.equal(result.type, 'boolean')
  })

  test('creates a boolean property with a value', ({ assert }) => {
    const result = Property.Boolean('a', false)
    assert.equal(result.value, false)
  })

  test('creates a boolean property with the enabled flag', ({ assert }) => {
    const result = Property.Boolean('a', true, false)
    assert.equal(result.enabled, false)
  })
})

test.group('Models > Property > Property.Date()', () => {
  test('creates a date property with defaults', ({ assert }) => {
    const result = Property.Date('a')
    assert.equal(result.kind, PropertyKind)
    assert.equal(result.name, 'a')
    assert.equal(result.value, '')
    assert.equal(result.enabled, true)
    assert.equal(result.type, 'date')
  })
})

test.group('Models > Property > Property.Datetime()', () => {
  test('creates a datetime property with defaults', ({ assert }) => {
    const result = Property.Datetime('a')
    assert.equal(result.kind, PropertyKind)
    assert.equal(result.name, 'a')
    assert.equal(result.value, '')
    assert.equal(result.enabled, true)
    assert.equal(result.type, 'datetime')
  })
})

test.group('Models > Property > Property.Time()', () => {
  test('creates a time property with defaults', ({ assert }) => {
    const result = Property.Time('a')
    assert.equal(result.kind, PropertyKind)
    assert.equal(result.name, 'a')
    assert.equal(result.value, '')
    assert.equal(result.enabled, true)
    assert.equal(result.type, 'time')
  })
})

test.group('Models > Property > Property.Bytes()', () => {
  test('creates a bytes property with defaults', ({ assert }) => {
    const result = Property.Bytes('a')
    assert.equal(result.kind, PropertyKind)
    assert.equal(result.name, 'a')
    assert.equal(result.value, '')
    assert.equal(result.enabled, true)
    assert.equal(result.type, 'bytes')
  })
})

test.group('Models > Property > Property.fromTypeDefault()', () => {
  const types = [
    { type: 'string', value: '' },
    { type: 'boolean', value: false },
    { type: 'date', value: '' },
    { type: 'datetime', value: '' },
    { type: 'time', value: '' },
    { type: 'float', value: 0.0 },
    { type: 'double', value: 0.0 },
    { type: 'integer', value: 0 },
    { type: 'int32', value: 0 },
    { type: 'int64', value: 0 },
    { type: 'uint32', value: 0 },
    { type: 'uint64', value: 0 },
    { type: 'sint32', value: 0 },
    { type: 'sint64', value: 0 },
    { type: 'fixed32', value: 0 },
    { type: 'fixed64', value: 0 },
    { type: 'sfixed32', value: 0 },
    { type: 'sfixed64', value: 0 },
    { type: 'bytes', value: '' },
  ]

  types.forEach(({ type, value }) => {
    test(`creates a ${type} property`, ({ assert }) => {
      const result = Property.fromTypeDefault('a', type as PropertyType)
      assert.equal(result.kind, PropertyKind)
      assert.equal(result.name, 'a')
      assert.equal(result.value, value)
      assert.equal(result.enabled, true)
      assert.equal(result.type, type)
    })
  })

  test('throws for an unknown type', ({ assert }) => {
    assert.throws(() => {
      // @ts-expect-error Used in testing
      Property.fromTypeDefault('a', 'unknown')
    })
  })
})

test.group('Models > Property > constructor()', () => {
  test('creates a default instance', ({ assert }) => {
    const result = new Property()
    assert.equal(result.kind, PropertyKind)
    assert.equal(result.name, '')
    assert.equal(result.value, '')
    assert.equal(result.type, 'string')
  })

  test('creates an instance from schema', ({ assert }) => {
    const init: IProperty = {
      kind: PropertyKind,
      name: 'a',
      type: 'string',
      value: '',
    }
    const result = new Property(init)
    assert.equal(result.kind, PropertyKind)
    assert.equal(result.name, 'a')
    assert.equal(result.type, 'string')
    assert.equal(result.value, '')
  })

  test('creates an instance from JSON schema', ({ assert }) => {
    const init: IProperty = {
      kind: PropertyKind,
      name: 'a',
      type: 'string',
      value: '',
    }
    const result = new Property(JSON.stringify(init))
    assert.equal(result.kind, PropertyKind)
    assert.equal(result.name, 'a')
    assert.equal(result.type, 'string')
    assert.equal(result.value, '')
  })
})

test.group('Models > Property > new()', (group) => {
  let property: Property
  group.each.setup(() => {
    property = Property.String('test-name', 'test-value', false)
  })

  test('throws when not an IProperty', ({ assert }) => {
    assert.throws(() => {
      // @ts-expect-error Used in testing
      property.new({})
    })
  })

  const stringProps: (keyof IProperty)[] = ['name', 'value', 'description', 'default']
  stringProps.forEach((prop) => {
    test(`sets the ${prop}`, ({ assert }) => {
      const schema = property.toJSON()
      // @ts-expect-error Used in testing
      schema[prop] = 'updated'
      property.new(schema)
      assert.equal(property[prop], 'updated')
    })
  })

  const booleanProps: (keyof IProperty)[] = ['enabled', 'required', 'repeated']
  booleanProps.forEach((prop) => {
    test(`sets the ${prop}`, ({ assert }) => {
      const schema = property.toJSON()
      // @ts-expect-error Used in testing
      schema[prop] = true
      property.new(schema)
      assert.equal(property[prop], true)
    })
  })

  test('sets the enum', ({ assert }) => {
    const schema = property.toJSON()
    schema.enum = ['updated']
    property.new(schema)
    assert.deepEqual(property.enum, ['updated'])
  })
})

test.group('Models > Property > Property.isProperty()', () => {
  test('returns false when no input', ({ assert }) => {
    const result = Property.isProperty(undefined)
    assert.isFalse(result)
  })

  test('returns false when no name', ({ assert }) => {
    const schema = Property.String('test').toJSON()
    // @ts-expect-error Used in testing
    delete schema.name
    const result = Property.isProperty(schema)
    assert.isFalse(result)
  })

  test('returns false when no type', ({ assert }) => {
    const schema = Property.String('test').toJSON()
    // @ts-expect-error Used in testing
    delete schema.type
    const result = Property.isProperty(schema)
    assert.isFalse(result)
  })

  test('returns false when invalid kind', ({ assert }) => {
    const schema = Property.String('test').toJSON()
    // @ts-expect-error Used in testing
    schema.kind = 'test'
    const result = Property.isProperty(schema)
    assert.isFalse(result)
  })

  test('returns false when unknown type', ({ assert }) => {
    const schema = Property.String('test').toJSON()
    // @ts-expect-error Used in testing
    schema.type = 'test'
    const result = Property.isProperty(schema)
    assert.isFalse(result)
  })

  test('returns true when a valid property', ({ assert }) => {
    const schema = Property.String('test').toJSON()
    const result = Property.isProperty(schema)
    assert.isTrue(result)
  })
})

test.group('Models > Property > toJSON()', (group) => {
  let instance: Property
  group.each.setup(() => {
    instance = new Property()
  })

  test('sets the kind', ({ assert }) => {
    const result = Property.String().toJSON()
    assert.equal(result.kind, PropertyKind)
  })

  test('sets the name', ({ assert }) => {
    const result = Property.String('test').toJSON()
    assert.equal(result.name, 'test')
  })

  test('sets the value', ({ assert }) => {
    const result = Property.String('', 'test').toJSON()
    assert.equal(result.value, 'test')
  })

  test('sets the type', ({ assert }) => {
    const result = Property.String().toJSON()
    assert.equal(result.type, 'string')
  })

  const optionalStringProps: (keyof IProperty)[] = ['description', 'default']
  optionalStringProps.forEach((prop) => {
    test(`does not set the ${prop} when missing`, ({ assert }) => {
      const result = instance.toJSON()
      assert.isUndefined(result[prop])
    })

    test(`sets the ${prop}`, ({ assert }) => {
      // @ts-expect-error Used in testing
      instance[prop] = 'test'
      const result = instance.toJSON()
      assert.equal(result[prop], 'test')
    })
  })

  test('does not set the enum when missing', ({ assert }) => {
    const result = instance.toJSON()
    assert.isUndefined(result.enum)
  })

  test('sets the enum', ({ assert }) => {
    instance.enum = ['test']
    const result = instance.toJSON()
    assert.deepEqual(result.enum, ['test'])
  })

  const optionalBooleanProps: (keyof IProperty)[] = ['enabled', 'required', 'repeated']
  optionalBooleanProps.forEach((prop) => {
    test(`does not set the ${prop} when missing`, ({ assert }) => {
      const result = instance.toJSON()
      assert.isUndefined(result[prop])
    })

    test(`sets the ${prop}`, ({ assert }) => {
      // @ts-expect-error Used in testing
      instance[prop] = true
      const result = instance.toJSON()
      assert.isTrue(result[prop])
    })
  })
})
