import { test } from '@japa/runner'
import {
  SemanticType,
  SemanticScope,
  SemanticCategory,
  SemanticTiming,
  SemanticOperation,
  isEntitySemantic,
  isPropertySemantic,
  isAssociationSemantic,
  DataSemantics,
  getSemanticsByCategory,
  getSemanticsByCategoryType,
  type EntitySemantic,
  type PropertySemantic,
  type AssociationSemantic,
  type DataSemantic,
} from '../../../src/modeling/Semantics.js'

test.group('Semantics', () => {
  test('SemanticType enum should have correct values', ({ assert }) => {
    assert.equal(SemanticType.User, 'Semantic#User')
    assert.equal(SemanticType.CreatedTimestamp, 'Semantic#CreatedTimestamp')
    assert.equal(SemanticType.UpdatedTimestamp, 'Semantic#UpdatedTimestamp')
    assert.equal(SemanticType.DeletedTimestamp, 'Semantic#DeletedTimestamp')
    assert.equal(SemanticType.DeletedFlag, 'Semantic#DeletedFlag')
    assert.equal(SemanticType.PublicUniqueName, 'Semantic#PublicUniqueName')
    assert.equal(SemanticType.Title, 'Semantic#Title')
    assert.equal(SemanticType.UserRole, 'Semantic#UserRole')
    assert.equal(SemanticType.Status, 'Semantic#Status')
    assert.equal(SemanticType.Version, 'Semantic#Version')
    assert.equal(SemanticType.ImageURL, 'Semantic#ImageURL')
    assert.equal(SemanticType.FileURL, 'Semantic#FileURL')
    assert.equal(SemanticType.Markdown, 'Semantic#Markdown')
    assert.equal(SemanticType.GeospatialCoordinates, 'Semantic#GeospatialCoordinates')
    assert.equal(SemanticType.Currency, 'Semantic#Currency')
    assert.equal(SemanticType.ResourceOwnerIdentifier, 'Semantic#ResourceOwnerIdentifier')
    assert.equal(SemanticType.SKU, 'Semantic#SKU')
  })

  test('SemanticScope enum should have correct values', ({ assert }) => {
    assert.equal(SemanticScope.Entity, 'Entity')
    assert.equal(SemanticScope.Property, 'Property')
    assert.equal(SemanticScope.Association, 'Association')
  })

  test('isEntitySemantic type guard', ({ assert }) => {
    const entitySemantic: EntitySemantic = {
      id: SemanticType.User,
      displayName: 'User Entity',
      description: 'Test',
      scope: SemanticScope.Entity,
      category: SemanticCategory.Identity,
      hasConfig: false,
      runtime: {
        timing: SemanticTiming.None,
        operations: [],
      },
    }
    const propertySemantic: PropertySemantic = {
      id: SemanticType.CreatedTimestamp,
      displayName: 'Creation Timestamp',
      description: 'Test',
      scope: SemanticScope.Property,
      category: SemanticCategory.Lifecycle,
      hasConfig: false,
      applicableDataTypes: ['datetime'],
      runtime: {
        timing: SemanticTiming.Before,
        operations: [SemanticOperation.Create],
        priority: 90,
      },
    }
    const associationSemantic: AssociationSemantic = {
      id: SemanticType.ResourceOwnerIdentifier,
      displayName: 'Resource Owner Identifier',
      description: 'Test',
      scope: SemanticScope.Association,
      category: SemanticCategory.Identity,
      hasConfig: false,
      runtime: {
        timing: SemanticTiming.Before,
        operations: [
          SemanticOperation.Create,
          SemanticOperation.Read,
          SemanticOperation.Update,
          SemanticOperation.Delete,
          SemanticOperation.List,
        ],
        priority: 5,
        canDisable: false,
      },
    }

    assert.isTrue(isEntitySemantic(entitySemantic))
    assert.isFalse(isEntitySemantic(propertySemantic))
    assert.isFalse(isEntitySemantic(associationSemantic))
  })

  test('isPropertySemantic type guard', ({ assert }) => {
    const entitySemantic: EntitySemantic = {
      id: SemanticType.User,
      displayName: 'User Entity',
      description: 'Test',
      scope: SemanticScope.Entity,
      category: SemanticCategory.Identity,
      hasConfig: false,
      runtime: {
        timing: SemanticTiming.None,
        operations: [],
      },
    }
    const propertySemantic: PropertySemantic = {
      id: SemanticType.CreatedTimestamp,
      displayName: 'Creation Timestamp',
      description: 'Test',
      scope: SemanticScope.Property,
      category: SemanticCategory.Lifecycle,
      hasConfig: false,
      applicableDataTypes: ['datetime'],
      runtime: {
        timing: SemanticTiming.Before,
        operations: [SemanticOperation.Create],
        priority: 90,
      },
    }
    const associationSemantic: AssociationSemantic = {
      id: SemanticType.ResourceOwnerIdentifier,
      displayName: 'Resource Owner Identifier',
      description: 'Test',
      scope: SemanticScope.Association,
      category: SemanticCategory.Identity,
      hasConfig: false,
      runtime: {
        timing: SemanticTiming.Before,
        operations: [
          SemanticOperation.Create,
          SemanticOperation.Read,
          SemanticOperation.Update,
          SemanticOperation.Delete,
          SemanticOperation.List,
        ],
        priority: 5,
        canDisable: false,
      },
    }

    assert.isFalse(isPropertySemantic(entitySemantic))
    assert.isTrue(isPropertySemantic(propertySemantic))
    assert.isFalse(isPropertySemantic(associationSemantic))
  })

  test('isAssociationSemantic type guard', ({ assert }) => {
    const entitySemantic: EntitySemantic = {
      id: SemanticType.User,
      displayName: 'User Entity',
      description: 'Test',
      scope: SemanticScope.Entity,
      category: SemanticCategory.Identity,
      hasConfig: false,
      runtime: {
        timing: SemanticTiming.None,
        operations: [],
      },
    }
    const propertySemantic: PropertySemantic = {
      id: SemanticType.CreatedTimestamp,
      displayName: 'Creation Timestamp',
      description: 'Test',
      scope: SemanticScope.Property,
      category: SemanticCategory.Lifecycle,
      hasConfig: false,
      applicableDataTypes: ['datetime'],
      runtime: {
        timing: SemanticTiming.Before,
        operations: [SemanticOperation.Create],
        priority: 90,
      },
    }
    const associationSemantic: AssociationSemantic = {
      id: SemanticType.ResourceOwnerIdentifier,
      displayName: 'Resource Owner Identifier',
      description: 'Test',
      scope: SemanticScope.Association,
      category: SemanticCategory.Identity,
      hasConfig: false,
      runtime: {
        timing: SemanticTiming.Before,
        operations: [
          SemanticOperation.Create,
          SemanticOperation.Read,
          SemanticOperation.Update,
          SemanticOperation.Delete,
          SemanticOperation.List,
        ],
        priority: 5,
        canDisable: false,
      },
    }

    assert.isFalse(isAssociationSemantic(entitySemantic))
    assert.isFalse(isAssociationSemantic(propertySemantic))
    assert.isTrue(isAssociationSemantic(associationSemantic))
  })

  test('DataSemantics should contain correct definitions', ({ assert }) => {
    const semanticTypes = Object.values(SemanticType)
    semanticTypes.forEach((type) => {
      const semantic: DataSemantic | undefined = DataSemantics[type]
      assert.isDefined(semantic, `Semantic definition for ${type} should exist`)
      if (!semantic) return

      assert.equal(semantic.id, type, `ID for ${type} should match`)
      assert.isString(semantic.displayName, `displayName for ${type} should be a string`)
      assert.isNotEmpty(semantic.displayName, `displayName for ${type} should not be empty`)
      assert.isString(semantic.description, `description for ${type} should be a string`)
      assert.isNotEmpty(semantic.description, `description for ${type} should not be empty`)

      assert.oneOf(semantic.scope, Object.values(SemanticScope), `scope for ${type} should be a valid SemanticScope`)
      assert.oneOf(
        semantic.category,
        Object.values(SemanticCategory),
        `category for ${type} should be a valid SemanticCategory`
      )

      if (isPropertySemantic(semantic)) {
        if (semantic.applicableDataTypes) {
          assert.isArray(semantic.applicableDataTypes, `applicableDataTypes for ${type} should be an array if present`)
          semantic.applicableDataTypes.forEach((dt) => {
            assert.isString(dt, `Each applicableDataType for ${type} should be a string`)
          })
        }
      }
    })

    // Specific checks for some semantics
    const userSemantic = DataSemantics[SemanticType.User]
    assert.equal(userSemantic.scope, SemanticScope.Entity)
    assert.equal(userSemantic.category, SemanticCategory.Identity)

    const createdTimestampSemantic = DataSemantics[SemanticType.CreatedTimestamp]
    assert.equal(createdTimestampSemantic.scope, SemanticScope.Property)
    assert.equal(createdTimestampSemantic.category, SemanticCategory.Lifecycle)
    if (isPropertySemantic(createdTimestampSemantic)) {
      assert.deepEqual(createdTimestampSemantic.applicableDataTypes, ['datetime'])
    }

    const resourceOwnerSemantic = DataSemantics[SemanticType.ResourceOwnerIdentifier]
    assert.equal(resourceOwnerSemantic.scope, SemanticScope.Association)
    assert.equal(resourceOwnerSemantic.category, SemanticCategory.Identity)
  })

  test('GeospatialCoordinates semantic should have correct definition', ({ assert }) => {
    const geospatialSemantic = DataSemantics[SemanticType.GeospatialCoordinates]

    // Check basic properties
    assert.equal(geospatialSemantic.id, SemanticType.GeospatialCoordinates)
    assert.equal(geospatialSemantic.displayName, 'Geospatial Coordinates')
    assert.equal(geospatialSemantic.scope, SemanticScope.Property)
    assert.isString(geospatialSemantic.description)
    assert.isNotEmpty(geospatialSemantic.description)

    // Check that it's a PropertySemantic
    assert.isTrue(isPropertySemantic(geospatialSemantic))

    // Check applicable data types
    if (isPropertySemantic(geospatialSemantic)) {
      assert.isDefined(geospatialSemantic.applicableDataTypes)
      assert.isArray(geospatialSemantic.applicableDataTypes)
      assert.deepEqual(geospatialSemantic.applicableDataTypes, ['string'])
    }
  })

  test('GeospatialCoordinates should work with type guards', ({ assert }) => {
    const geospatialSemantic = DataSemantics[SemanticType.GeospatialCoordinates]

    // Should be identified as a PropertySemantic
    assert.isTrue(isPropertySemantic(geospatialSemantic))
    assert.isFalse(isEntitySemantic(geospatialSemantic))
    assert.isFalse(isAssociationSemantic(geospatialSemantic))
  })

  test('GeospatialCoordinates should be included in all semantic types', ({ assert }) => {
    const semanticTypes = Object.values(SemanticType)
    assert.include(semanticTypes, SemanticType.GeospatialCoordinates)
  })

  test('GeospatialCoordinates should have comprehensive description', ({ assert }) => {
    const geospatialSemantic = DataSemantics[SemanticType.GeospatialCoordinates]
    const description = geospatialSemantic.description

    // Check that the description mentions key features
    assert.equal(description, 'Location coordinates')
  })

  test('getSemanticsByCategory should group semantics correctly', ({ assert }) => {
    const semanticsByCategory = getSemanticsByCategory()

    // Check that all categories are present
    const expectedCategories = Object.values(SemanticCategory)
    expectedCategories.forEach((category) => {
      assert.isDefined(semanticsByCategory[category], `Category ${category} should be present`)
      assert.isArray(semanticsByCategory[category], `Category ${category} should be an array`)
    })

    // Check that User semantic is in Identity category
    const identitySemantics = semanticsByCategory[SemanticCategory.Identity]
    const userSemantic = identitySemantics.find((s) => s.id === SemanticType.User)
    assert.isDefined(userSemantic, 'User semantic should be in Identity category')

    // Check that CreatedTimestamp is in Lifecycle category
    const lifecycleSemantics = semanticsByCategory[SemanticCategory.Lifecycle]
    const createdTimestampSemantic = lifecycleSemantics.find((s) => s.id === SemanticType.CreatedTimestamp)
    assert.isDefined(createdTimestampSemantic, 'CreatedTimestamp semantic should be in Lifecycle category')

    // Check that Title is in Content category
    const contentSemantics = semanticsByCategory[SemanticCategory.Content]
    const titleSemantic = contentSemantics.find((s) => s.id === SemanticType.Title)
    assert.isDefined(titleSemantic, 'Title semantic should be in Content category')

    // Verify all semantics are categorized (total count should match)
    const totalSemantics = Object.values(semanticsByCategory).flat().length
    const allSemantics = Object.values(DataSemantics).length
    assert.equal(totalSemantics, allSemantics, 'All semantics should be categorized')
  })

  test('getSemanticsByCategory should filter by Entity scope', ({ assert }) => {
    const semanticsByCategory = getSemanticsByCategory(SemanticScope.Entity)
    const allEntitySemantics = Object.values(DataSemantics).filter((s) => s.scope === SemanticScope.Entity)

    // Ensure only Entity scope semantics are present
    Object.values(semanticsByCategory).forEach((categorySemantics) => {
      categorySemantics.forEach((semantic) => {
        assert.equal(semantic.scope, SemanticScope.Entity, 'Only Entity semantics should be returned')
      })
    })

    // Ensure all Entity scope semantics are returned
    const returnedEntitySemantics = Object.values(semanticsByCategory).flat()
    assert.equal(returnedEntitySemantics.length, allEntitySemantics.length, 'All Entity semantics should be returned')
    allEntitySemantics.forEach((expectedSemantic) => {
      assert.include(
        returnedEntitySemantics,
        expectedSemantic,
        `Expected Entity semantic ${expectedSemantic.id} not found`
      )
    })
  })

  test('getSemanticsByCategory should filter by Property scope', ({ assert }) => {
    const semanticsByCategory = getSemanticsByCategory(SemanticScope.Property)
    const allPropertySemantics = Object.values(DataSemantics).filter((s) => s.scope === SemanticScope.Property)

    // Ensure only Property scope semantics are present
    Object.values(semanticsByCategory).forEach((categorySemantics) => {
      categorySemantics.forEach((semantic) => {
        assert.equal(semantic.scope, SemanticScope.Property, 'Only Property semantics should be returned')
      })
    })

    // Ensure all Property scope semantics are returned
    const returnedPropertySemantics = Object.values(semanticsByCategory).flat()
    assert.equal(
      returnedPropertySemantics.length,
      allPropertySemantics.length,
      'All Property semantics should be returned'
    )
    allPropertySemantics.forEach((expectedSemantic) => {
      assert.include(
        returnedPropertySemantics,
        expectedSemantic,
        `Expected Property semantic ${expectedSemantic.id} not found`
      )
    })
  })

  test('getSemanticsByCategory should filter by Association scope', ({ assert }) => {
    const semanticsByCategory = getSemanticsByCategory(SemanticScope.Association)
    const allAssociationSemantics = Object.values(DataSemantics).filter((s) => s.scope === SemanticScope.Association)

    // Ensure only Association scope semantics are present
    Object.values(semanticsByCategory).forEach((categorySemantics) => {
      categorySemantics.forEach((semantic) => {
        assert.equal(semantic.scope, SemanticScope.Association, 'Only Association semantics should be returned')
      })
    })

    // Ensure all Association scope semantics are returned
    const returnedAssociationSemantics = Object.values(semanticsByCategory).flat()
    assert.equal(
      returnedAssociationSemantics.length,
      allAssociationSemantics.length,
      'All Association semantics should be returned'
    )
    allAssociationSemantics.forEach((expectedSemantic) => {
      assert.include(
        returnedAssociationSemantics,
        expectedSemantic,
        `Expected Association semantic ${expectedSemantic.id} not found`
      )
    })
  })

  test('getSemanticsByCategoryType should filter semantics by category', ({ assert }) => {
    const identitySemantics = getSemanticsByCategoryType(SemanticCategory.Identity)

    // All returned semantics should be in Identity category
    identitySemantics.forEach((semantic) => {
      assert.equal(semantic.category, SemanticCategory.Identity, 'All semantics should be in Identity category')
    })

    // Should include User semantic
    const userSemantic = identitySemantics.find((s) => s.id === SemanticType.User)
    assert.isDefined(userSemantic, 'Should include User semantic')

    // Should include Password semantic
    const passwordSemantic = identitySemantics.find((s) => s.id === SemanticType.Password)
    assert.isDefined(passwordSemantic, 'Should include Password semantic')

    // Test empty result for a category with no semantics (if any)
    const locationSemantics = getSemanticsByCategoryType(SemanticCategory.Location)
    assert.isArray(locationSemantics, 'Should return an array even if empty')
  })
})
