import { Ability } from '@casl/ability'
import { $abilitiesUtils } from '~/services/rights/abilitiesUtils'
import { createStore } from '~/tests/unit/Helpers'
import { AnyStore } from '~/types/interfaces'
import { $accessProfile } from '~/services/profile/accessProfile'
import { $organizationProfile } from '~/services/profile/organizationProfile'
import { accessProfile as accessModule, organizationProfile as organizationModule } from '~/tests/unit/fixture/state/profile'
import { $roleUtils } from '~/services/rights/roleUtils'

let ability: Ability, store: AnyStore, abilitiesUtils: any

beforeEach(() => {
  ability = new Ability()
  store = createStore()
  store.registerModule('profile', {})
  store.registerModule(['profile', 'access'], accessModule)
  store.registerModule(['profile', 'organization'], organizationModule)
  abilitiesUtils = $abilitiesUtils(store, ability)
})

describe('initFactory', () => {
  beforeEach(() => {
    abilitiesUtils.initFactory()
  })
  it('should init the factory service and give access to AccessProfile service', () => {
    expect(abilitiesUtils.getFactory().access).toStrictEqual($accessProfile)
  })

  it('should init the factory service and give access to OrganizationProfile service', () => {
    expect(abilitiesUtils.getFactory().organization).toStrictEqual($organizationProfile(store))
  })
})

describe('setAbilities', () => {
  it('should call ability.update once', () => {
    ability.update = jest.fn().mockReturnValue([])
    abilitiesUtils.setAbilities()
    expect(ability.update).toBeCalledTimes(1)
  })
  it('should call store.subscribeAction once', () => {
    store.subscribeAction = jest.fn().mockReturnValue([])
    abilitiesUtils.setAbilities()
    expect(store.subscribeAction).toBeCalledTimes(1)
  })
})

describe('getAbilities()', () => {
  it('should call getAbilitiesByRoles once', () => {
    abilitiesUtils.getAbilitiesByRoles = jest.fn().mockReturnValue([])
    abilitiesUtils.getAbilities()
    expect(abilitiesUtils.getAbilitiesByRoles).toBeCalledTimes(1)
  })

  it('should call getAbilitiesByConfig once', () => {
    abilitiesUtils.getAbilitiesByConfig = jest.fn()
    abilitiesUtils.getAbilities()
    expect(abilitiesUtils.getAbilitiesByConfig).toBeCalledTimes(1)
  })

  it('should concat abilities', () => {
    abilitiesUtils.getAbilitiesByRoles = jest.fn().mockReturnValue([{
      action: 'manage',
      subject: 'user'
    }])
    abilitiesUtils.getAbilitiesByConfig = jest.fn().mockReturnValue([{
      action: 'manage',
      subject: 'bills'
    }])
    expect(abilitiesUtils.getAbilities()).toStrictEqual([{
      action: 'manage',
      subject: 'user'
    }, {
      action: 'manage',
      subject: 'bills'
    }])
  })
})

describe('getAbilitiesByRoles()', () => {
  it('should call $roleUtils.transformUnderscoreToHyphenBeforeCompleteMigration once', () => {
    $roleUtils.transformUnderscoreToHyphenBeforeCompleteMigration = jest.fn().mockReturnValue([])
    abilitiesUtils.getAbilitiesByRoles()
    expect($roleUtils.transformUnderscoreToHyphenBeforeCompleteMigration).toBeCalledTimes(1)
  })

  it('should call $roleUtils.transformRoleToAbilities once', () => {
    $roleUtils.transformRoleToAbilities = jest.fn().mockReturnValue([])
    abilitiesUtils.getAbilitiesByRoles()
    expect($roleUtils.transformRoleToAbilities).toBeCalledTimes(1)
  })
})

describe('getAbilitiesByConfig()', () => {
  it('should throw an error if the path is wrong', () => {
    expect(() => abilitiesUtils.getAbilitiesByConfig('wrong_path')).toThrowError()
  })
})

describe('transformAbilitiesConfigToAbility', () => {
  it('should transform a config array to an Abilities array', () => {
    const abilitiesConfig = {
      accesses: {
        action: 'display',
        services: {
          access: [
            {
              function: 'hasAbility',
              parameters: {
                action: 'read',
                subject: 'user'
              }
            }
          ],
          organization: [
            {
              function: 'hasModule',
              parameters: [
                'Users'
              ]
            }
          ]
        }
      },
      student_registration: {
        action: 'display',
        services: {
          access: [
            {
              function: 'hasAbility',
              parameters: {
                action: 'read',
                subject: 'student-registration'
              }
            }
          ],
          organization: [
            {
              function: 'hasModule',
              parameters: [
                'UsersSchool'
              ]
            }
          ]
        }
      }
    }
    const abilities_to_have = [
      { action: 'display', subject: 'accesses' },
      { action: 'display', subject: 'student_registration' }
    ]

    expect(abilitiesUtils.transformAbilitiesConfigToAbility(abilitiesConfig)).toStrictEqual(abilities_to_have)
  })
})

describe('canHaveTheAbility()', () => {
  const functionsServices = {
    access: [
      { function: 'hasAbility', parameters: [{ action: 'read', subject: 'users' }] }
    ],
    organization: [
      { function: 'hasModule', parameters: ['Users'] }
    ]
  }

  beforeEach(() => {
    abilitiesUtils.initFactory()
  })

  it('should return false when we dont have the ability', () => {
    expect(abilitiesUtils.canHaveTheAbility(functionsServices)).toBeFalsy()
  })

  it('should return false when we user have the ability but organization dont have the module', () => {
    ability.update([{ action: 'manage', subject: 'users' }])
    expect(abilitiesUtils.canHaveTheAbility(functionsServices)).toBeFalsy()
  })

  it('should return true when we user have the ability and organization have the module', () => {
    ability.update([{ action: 'manage', subject: 'users' }])
    store.commit('organization/setModules', ['Users'])
    expect(abilitiesUtils.canHaveTheAbility(functionsServices)).toBeTruthy()
  })
})
