import { describe, test, expect } from 'vitest'
import {MongoAbility} from "@casl/ability/dist/types/Ability";
import {AbilitiesType, AccessProfile, organizationState} from "~/types/interfaces";
import AbilityBuilder from "~/services/rights/abilityBuilder";
import {ABILITIES} from "~/types/enum/enums";

let ability: MongoAbility
let accessProfile: AccessProfile
let organizationProfile: organizationState
let abilityBuilder: AbilityBuilder

// Mock the content of the config yaml files
// > This must be done in the global scope: https://vitest.dev/api/vi.html#vi-mock
const doc = {
    abilities: {
        'subject1': {
            action: ABILITIES.READ,
            conditions: [
                {
                    'function': 'fct1',
                    'parameters': ['param1'],
                    'expectedResult': true
                }
            ]
        },
        'subject2': {
            action: ABILITIES.READ,
            conditions: {
                'function': 'fct2',
                'parameters': ['param2'],
                'expectedResult': false
            }
        }
    }
}
vi.mock('yaml-import', async () => {
    return {
        default: { read: vi.fn((data: string) => doc) }
    }
})

beforeEach(() => {
    ability = vi.fn() as any as MongoAbility
    accessProfile = vi.fn() as any as AccessProfile
    organizationProfile = vi.fn() as any as organizationState

    abilityBuilder = new AbilityBuilder(ability, accessProfile, organizationProfile)
})

describe('buildAbilities', () => {
    test('base call', () => {

        const roleAbilities: Array<AbilitiesType> = [
            {action: ABILITIES.READ, subject: 'subject1'},
            {action: ABILITIES.READ, subject: 'subject2'}
        ]
        const configAbilities: Array<AbilitiesType> = [
            {action: ABILITIES.READ, subject: 'subject3'},
            {action: ABILITIES.READ, subject: 'subject4'}
        ]
        const allAbilities: Array<AbilitiesType> = roleAbilities.concat(configAbilities)


        abilityBuilder.buildAbilitiesFromRoles = vi.fn(() => roleAbilities)
        abilityBuilder.buildAbilitiesFromConfig = vi.fn(() => configAbilities)

        ability.update = vi.fn()

        const result = abilityBuilder.buildAbilities()

        expect(ability.update).toHaveBeenCalledTimes(2)
        expect(ability.update).toHaveBeenCalledWith(roleAbilities)
        expect(ability.update).toHaveBeenCalledWith(allAbilities)

        expect(abilityBuilder.buildAbilitiesFromRoles).toHaveBeenCalledOnce()
        expect(abilityBuilder.buildAbilitiesFromConfig).toHaveBeenCalledOnce()

        expect(result).toEqual(allAbilities)
    })
})

describe('buildAbilitiesFromRoles', () => {
  test('calls roleUtils', () => {
      accessProfile.roles = ['ROLE_EVENTS_VIEW', 'ROLE_COURSES', 'ROLE_TEACHER_CORE', 'ROLE_OTHER']

      const expected = [
          { subject: 'events', action: 'read' },
          { subject: 'courses', action: 'manage' },
          { subject: 'other', action: 'manage' },
      ]

      expect(abilityBuilder.buildAbilitiesFromRoles()).toEqual(expected)
  })
})

describe('buildAbilitiesFromConfig', () => {
  test('calls roleUtils', () => {
      abilityBuilder.hasConfigAbility = vi.fn(() => true)

      expect(abilityBuilder.buildAbilitiesFromConfig()).toEqual([
          { action: 'read', subject: 'subject1' },
          { action: 'read', subject: 'subject2' },
      ])
  })
})

describe('hasConfigAbility', () => {
    beforeEach(() => {
        accessProfile.isGuardian = true
        // @ts-ignore
        organizationProfile.isSchool = true
        // @ts-ignore
        organizationProfile.isCmf = false
    })
    test('fulfill all conditions', () => {
        const conditions = [
            {'function': 'accessHasAnyProfile', parameters: ['guardian', 'payer']},
            {'function': 'organizationIsSchool'},
        ]

        expect(abilityBuilder.hasConfigAbility(conditions)).toBeTruthy()
    })
    test('fulfill at least one condition', () => {
        const conditions = [
            {'function': 'accessHasAnyProfile', parameters: ['guardian', 'payer']},
            {'function': 'organizationIsCmf'},
        ]

        expect(abilityBuilder.hasConfigAbility(conditions)).toBeFalsy()
    })
    test('fulfill none of the conditions', () => {
        const conditions = [
            {'function': 'organizationIsCmf'},
        ]

        expect(abilityBuilder.hasConfigAbility(conditions)).toBeFalsy()
    })
})

describe('execAndValidateCondition', () => {
    test('accessHasAllRoleAbilities', () => {
        ability.can = vi.fn((action: string, subject: string) => {
            return action === 'read' && (subject === 'subject1' || subject === 'subject2')
        })

        expect(
            // @ts-ignore
            abilityBuilder.execAndValidateCondition(
                {
                    'function': 'accessHasAllRoleAbilities',
                    parameters: [
                        {action: ABILITIES.READ, subject: 'subject1'},
                        {action: ABILITIES.READ, subject: 'subject2'},
                    ]
                })
        ).toBeTruthy()

        expect(
            // @ts-ignore
            abilityBuilder.execAndValidateCondition(
                {
                    'function': 'accessHasAllRoleAbilities',
                    parameters: [
                    {action: ABILITIES.READ, subject: 'subject1'},
                    {action: ABILITIES.READ, subject: 'subject3'}
                ]
            })
        ).toBeFalsy()
    })

    test('accessHasAnyRoleAbility', () => {
        ability.can = vi.fn((action: string, subject: string) => {
            return action === 'read' && subject === 'subject1'
        })

        expect(
            // @ts-ignore
            abilityBuilder.execAndValidateCondition(
                {
                    'function': 'accessHasAnyRoleAbility',
                    parameters: [
                        {action: ABILITIES.READ, subject: 'subject1'},
                        {action: ABILITIES.READ, subject: 'subject2'},
                    ]
                })
        ).toBeTruthy()

        expect(
            // @ts-ignore
            abilityBuilder.execAndValidateCondition(
                {'function': 'accessHasAnyRoleAbility', parameters: [{action: ABILITIES.READ, subject: 'subject2'}]})
        ).toBeFalsy()
    })

    test('accessHasAnyProfile', () => {
        accessProfile.isMember = true
        accessProfile.isGuardian = true
        accessProfile.isPayer = true

        expect(
            // @ts-ignore
            abilityBuilder.execAndValidateCondition(
                {'function': 'accessHasAnyProfile', parameters: ['guardian', 'payer']}
            )
        ).toBeTruthy()

        expect(
            // @ts-ignore
            abilityBuilder.execAndValidateCondition(
                {'function': 'accessHasAnyProfile', parameters: ['guardian', 'caMember']}
            )
        ).toBeTruthy()

        expect(
            // @ts-ignore
            abilityBuilder.execAndValidateCondition(
                {'function': 'accessHasAnyProfile', parameters: ['caMember']}
            )
        ).toBeFalsy()
    })

    test('organizationHasAllModules', () => {
        // @ts-ignore
        organizationProfile.hasModule = vi.fn(
            (module: string) => module === 'module1' || module === 'module2'
        )

        expect(
            // @ts-ignore
            abilityBuilder.execAndValidateCondition(
                {'function': 'organizationHasAllModules', parameters: ['module1', 'module2']}
            )
        ).toBeTruthy()

        expect(
            // @ts-ignore
            abilityBuilder.execAndValidateCondition(
                {'function': 'organizationHasAllModules', parameters: ['module1', 'module3']}
            )
        ).toBeFalsy()

        expect(
            // @ts-ignore
            abilityBuilder.execAndValidateCondition(
                {'function': 'organizationHasAllModules', parameters: ['module3']}
            )
        ).toBeFalsy()
    })

    test('organizationHasAnyModule', () => {
        // @ts-ignore
        organizationProfile.hasModule = vi.fn(
            (module: string) => module === 'module1' || module === 'module2'
        )

        expect(
            // @ts-ignore
            abilityBuilder.execAndValidateCondition(
                {'function': 'organizationHasAnyModule', parameters: ['module1', 'module2']}
            )
        ).toBeTruthy()

        expect(
            // @ts-ignore
            abilityBuilder.execAndValidateCondition(
                {'function': 'organizationHasAnyModule', parameters: ['module1', 'module3']}
            )
        ).toBeTruthy()

        expect(
            // @ts-ignore
            abilityBuilder.execAndValidateCondition(
                {'function': 'organizationHasAnyModule', parameters: ['module3']}
            )
        ).toBeFalsy()
    })

    test('organizationHasAnyModule', () => {
        // @ts-ignore
        accessProfile.isAdminAccount = true
        // @ts-ignore
        expect(abilityBuilder.execAndValidateCondition({'function': 'accessIsAdminAccount'})).toBeTruthy()

        // @ts-ignore
        accessProfile.isAdminAccount = false
        // @ts-ignore
        expect(abilityBuilder.execAndValidateCondition({'function': 'accessIsAdminAccount'})).toBeFalsy()
    })

    test('organizationIsSchool', () => {
        // @ts-ignore
        organizationProfile.isSchool = true
        // @ts-ignore
        expect(abilityBuilder.execAndValidateCondition({'function': 'organizationIsSchool'})).toBeTruthy()

        // @ts-ignore
        organizationProfile.isSchool = false
        // @ts-ignore
        expect(abilityBuilder.execAndValidateCondition({'function': 'organizationIsSchool'})).toBeFalsy()
    })

    test('organizationIsArtist', () => {
        // @ts-ignore
        organizationProfile.isArtist = true
        // @ts-ignore
        expect(abilityBuilder.execAndValidateCondition({'function': 'organizationIsArtist'})).toBeTruthy()

        // @ts-ignore
        organizationProfile.isArtist = false
        // @ts-ignore
        expect(abilityBuilder.execAndValidateCondition({'function': 'organizationIsArtist'})).toBeFalsy()
    })

    test('organizationIsManagerProduct', () => {
        // @ts-ignore
        organizationProfile.isManagerProduct = true
        // @ts-ignore
        expect(abilityBuilder.execAndValidateCondition({'function': 'organizationIsManagerProduct'})).toBeTruthy()

        // @ts-ignore
        organizationProfile.isManagerProduct = false
        // @ts-ignore
        expect(abilityBuilder.execAndValidateCondition({'function': 'organizationIsManagerProduct'})).toBeFalsy()
    })

    test('organizationHasChildren', () => {
        // @ts-ignore
        organizationProfile.hasChildren = true
        // @ts-ignore
        expect(abilityBuilder.execAndValidateCondition({'function': 'organizationHasChildren'})).toBeTruthy()

        // @ts-ignore
        organizationProfile.hasChildren = false
        // @ts-ignore
        expect(abilityBuilder.execAndValidateCondition({'function': 'organizationHasChildren'})).toBeFalsy()
    })

    test('organizationIsAssociation', () => {
        // @ts-ignore
        organizationProfile.isAssociation = true
        // @ts-ignore
        expect(abilityBuilder.execAndValidateCondition({'function': 'organizationIsAssociation'})).toBeTruthy()

        // @ts-ignore
        organizationProfile.isAssociation = false
        // @ts-ignore
        expect(abilityBuilder.execAndValidateCondition({'function': 'organizationIsAssociation'})).toBeFalsy()
    })

    test('organizationIsShowAdherentList', () => {
        // @ts-ignore
        organizationProfile.isShowAdherentList = true
        // @ts-ignore
        expect(abilityBuilder.execAndValidateCondition({'function': 'organizationIsShowAdherentList'})).toBeTruthy()

        // @ts-ignore
        organizationProfile.isShowAdherentList = false
        // @ts-ignore
        expect(abilityBuilder.execAndValidateCondition({'function': 'organizationIsShowAdherentList'})).toBeFalsy()
    })

    test('organizationIsCmf', () => {
        // @ts-ignore
        organizationProfile.isCmf = true
        // @ts-ignore
        expect(abilityBuilder.execAndValidateCondition({'function': 'organizationIsCmf'})).toBeTruthy()

        // @ts-ignore
        organizationProfile.isCmf = false
        // @ts-ignore
        expect(abilityBuilder.execAndValidateCondition({'function': 'organizationIsCmf'})).toBeFalsy()
    })

    test('organizationHasWebsite', () => {
        // @ts-ignore
        organizationProfile.getWebsite = true
        // @ts-ignore
        expect(abilityBuilder.execAndValidateCondition({'function': 'organizationHasWebsite'})).toBeTruthy()

        // @ts-ignore
        organizationProfile.getWebsite = false
        // @ts-ignore
        expect(abilityBuilder.execAndValidateCondition({'function': 'organizationHasWebsite'})).toBeFalsy()
    })

    test('with expected result', () => {
        // @ts-ignore
        organizationProfile.getWebsite = true

        expect(
            // @ts-ignore
            abilityBuilder.execAndValidateCondition({'function': 'organizationHasWebsite', expectedResult: true})
        ).toBeTruthy()

        expect(
            // @ts-ignore
            abilityBuilder.execAndValidateCondition({'function': 'organizationHasWebsite', expectedResult: 'abc'})
        ).toBeFalsy()
    })

    test('invalid function', () => {
        expect(
            // @ts-ignore
            () => abilityBuilder.execAndValidateCondition({'function': 'invalid'})
        ).toThrowError('unknown condition function : invalid')
    })
})

describe('hasRoleAbility', () => {
    beforeEach(() => {
        ability.can = vi.fn((action: string, subject: string) => {
            return action === 'read' && subject === 'a_subject'
        })
    })

    test('owned ability', () => {
        expect(abilityBuilder.hasRoleAbility({action: ABILITIES.READ, subject: 'a_subject'})).toBeTruthy()
    })

    test('not owned ability', () => {
        expect(abilityBuilder.hasRoleAbility({action: ABILITIES.READ, subject: 'other_subject'})).toBeFalsy()
    })
})

describe('hasAllRoleAbilities', () => {
    beforeEach(() => {
        ability.can = vi.fn((action: string, subject: string) => {
            return action === 'read' && (subject === 'subject1' || subject === 'subject2')
        })
    })

    test('own all abilities', () => {
        const result = abilityBuilder.hasAllRoleAbilities(
            [
                {action: ABILITIES.READ, subject: 'subject1'},
                {action: ABILITIES.READ, subject: 'subject2'},
            ]
        )

        expect(result).toBeTruthy()
    })

    test('own at least one ability', () => {
        const result = abilityBuilder.hasAllRoleAbilities(
            [
                {action: ABILITIES.READ, subject: 'subject1'},
                {action: ABILITIES.READ, subject: 'subject3'},
            ]
        )

        expect(result).toBeFalsy()
    })

    test('own none of the abilities', () => {
        const result = abilityBuilder.hasAllRoleAbilities(
            [
                {action: ABILITIES.READ, subject: 'subject3'},
                {action: ABILITIES.READ, subject: 'subject4'},
            ]
        )

        expect(result).toBeFalsy()
    })
})

describe('hasAnyRoleAbility', () => {
    beforeEach(() => {
        ability.can = vi.fn((action: string, subject: string) => {
            return action === 'read' && (subject === 'subject1' || subject === 'subject2')
        })
    })

    test('has all abilities', () => {
        const result = abilityBuilder.hasAnyRoleAbility(
            [
                {action: ABILITIES.READ, subject: 'subject1'},
                {action: ABILITIES.READ, subject: 'subject2'},
            ]
        )

        expect(result).toBeTruthy()
    })

    test('has at least one ability', () => {
        const result = abilityBuilder.hasAnyRoleAbility(
            [
                {action: ABILITIES.READ, subject: 'subject1'},
                {action: ABILITIES.READ, subject: 'subject3'},
            ]
        )

        expect(result).toBeTruthy()
    })

    test('any none of the abilites', () => {
        const result = abilityBuilder.hasAnyRoleAbility(
            [
                {action: ABILITIES.READ, subject: 'subject3'},
                {action: ABILITIES.READ, subject: 'subject4'},
            ]
        )

        expect(result).toBeFalsy()
    })
})

describe('hasProfile', () => {
    test('owned profiles', () => {
        accessProfile.isAdmin = true
        accessProfile.isAdministratifManager = true
        accessProfile.isPedagogicManager = true
        accessProfile.isFinancialManager = true
        accessProfile.isCaMember = true
        accessProfile.isStudent = true
        accessProfile.isTeacher = true
        accessProfile.isMember = true
        accessProfile.isOther = true
        accessProfile.isGuardian = true
        accessProfile.isPayer = true

        expect(abilityBuilder.hasProfile('admin')).toBeTruthy()
        expect(abilityBuilder.hasProfile('administratifManager')).toBeTruthy()
        expect(abilityBuilder.hasProfile('pedagogicManager')).toBeTruthy()
        expect(abilityBuilder.hasProfile('financialManager')).toBeTruthy()
        expect(abilityBuilder.hasProfile('caMember')).toBeTruthy()
        expect(abilityBuilder.hasProfile('student')).toBeTruthy()
        expect(abilityBuilder.hasProfile('teacher')).toBeTruthy()
        expect(abilityBuilder.hasProfile('member')).toBeTruthy()
        expect(abilityBuilder.hasProfile('other')).toBeTruthy()
        expect(abilityBuilder.hasProfile('guardian')).toBeTruthy()
        expect(abilityBuilder.hasProfile('payor')).toBeTruthy()
    })

    test('not owned profiles', () => {
        accessProfile.isAdmin = false
        accessProfile.isAdministratifManager = false
        accessProfile.isPedagogicManager = false
        accessProfile.isFinancialManager = false
        accessProfile.isCaMember = false
        accessProfile.isStudent = false
        accessProfile.isTeacher = false
        accessProfile.isMember = false
        accessProfile.isOther = false
        accessProfile.isGuardian = false
        accessProfile.isPayer = false

        expect(abilityBuilder.hasProfile('admin')).toBeFalsy()
        expect(abilityBuilder.hasProfile('administratifManager')).toBeFalsy()
        expect(abilityBuilder.hasProfile('pedagogicManager')).toBeFalsy()
        expect(abilityBuilder.hasProfile('financialManager')).toBeFalsy()
        expect(abilityBuilder.hasProfile('caMember')).toBeFalsy()
        expect(abilityBuilder.hasProfile('student')).toBeFalsy()
        expect(abilityBuilder.hasProfile('teacher')).toBeFalsy()
        expect(abilityBuilder.hasProfile('member')).toBeFalsy()
        expect(abilityBuilder.hasProfile('other')).toBeFalsy()
        expect(abilityBuilder.hasProfile('guardian')).toBeFalsy()
        expect(abilityBuilder.hasProfile('payor')).toBeFalsy()
    })
})

describe('hasAnyProfile', () => {
    beforeEach(() => {
        accessProfile.isMember = true
        accessProfile.isGuardian = true
        accessProfile.isPayer = true
    })

    test('own all profiles', () => {
        expect(abilityBuilder.hasAnyProfile(['member', 'guardian', 'payor'])).toBeTruthy()
    })

    test('own at least one profile', () => {
        expect(abilityBuilder.hasAnyProfile(['member', 'caMember'])).toBeTruthy()
    })

    test('own none of the profiles', () => {
        expect(abilityBuilder.hasAnyProfile(['caMember', 'isFinancialManager'])).toBeFalsy()
    })
})

describe('hasAllProfiles', () => {
    beforeEach(() => {
        accessProfile.isMember = true
        accessProfile.isGuardian = true
        accessProfile.isPayer = true
    })

    test('own all profiles', () => {
        expect(abilityBuilder.hasAllProfiles(['member', 'guardian', 'payor'])).toBeTruthy()
    })

    test('own only one of the profiles', () => {
        expect(abilityBuilder.hasAllProfiles(['member', 'caMember'])).toBeFalsy()
    })

    test('own none of the profiles', () => {
        expect(abilityBuilder.hasAllProfiles(['caMember', 'isFinancialManager'])).toBeFalsy()
    })
})

describe('hasRole', () => {
    beforeEach(() => {
        // @ts-ignore
        accessProfile.hasRole = vi.fn((role: string) => role === 'foo')
    })

    test('has role', () => {
        expect(abilityBuilder.hasRole('foo')).toBeTruthy()
    })
    test('has not role', () => {
        expect(abilityBuilder.hasRole('bar')).toBeFalsy()
    })
})

describe('hasAnyRole', () => {
    beforeEach(() => {
        // @ts-ignore
        accessProfile.hasRole = vi.fn((role: string) => role === 'role1' || role === 'role2')
    })

    test('own all roles', () => {
        expect(abilityBuilder.hasAnyRole(['role1', 'role2'])).toBeTruthy()
    })

    test('own at least one role', () => {
        expect(abilityBuilder.hasAnyRole(['role1', 'role3'])).toBeTruthy()
    })

    test('own none of the roles', () => {
        expect(abilityBuilder.hasAnyRole(['role3'])).toBeFalsy()
    })
})

describe('hasAllRoles', () => {
    beforeEach(() => {
        // @ts-ignore
        accessProfile.hasRole = vi.fn((role: string) => role === 'role1' || role === 'role2')
    })

    test('own all roles', () => {
        expect(abilityBuilder.hasAllRoles(['role1', 'role2'])).toBeTruthy()
    })

    test('own at least one role', () => {
        expect(abilityBuilder.hasAllRoles(['role1', 'role3'])).toBeFalsy()
    })

    test('own none of the roles', () => {
        expect(abilityBuilder.hasAllRoles(['role3'])).toBeFalsy()
    })
})

describe('hasModule', () => {
    beforeEach(() => {
        // @ts-ignore
        organizationProfile.hasModule = vi.fn((module: string) => module === 'foo')
    })

    test('has module', () => {
        expect(abilityBuilder.hasModule('foo')).toBeTruthy()
    })
    test('has not module', () => {
        expect(abilityBuilder.hasModule('bar')).toBeFalsy()
    })
})

describe('hasAnyModule', () => {
    beforeEach(() => {
        // @ts-ignore
        organizationProfile.hasModule = vi.fn((Module: string) => Module === 'Module1' || Module === 'Module2')
    })

    test('own all modules', () => {
        expect(abilityBuilder.hasAnyModule(['Module1', 'Module2'])).toBeTruthy()
    })

    test('own at least one module', () => {
        expect(abilityBuilder.hasAnyModule(['Module1', 'Module3'])).toBeTruthy()
    })

    test('own none of the modules', () => {
        expect(abilityBuilder.hasAnyModule(['Module3'])).toBeFalsy()
    })
})

describe('hasAllModules', () => {
    beforeEach(() => {
        // @ts-ignore
        organizationProfile.hasModule = vi.fn((Module: string) => Module === 'Module1' || Module === 'Module2')
    })

    test('own all modules', () => {
        expect(abilityBuilder.hasAllModules(['Module1', 'Module2'])).toBeTruthy()
    })

    test('own at least one module', () => {
        expect(abilityBuilder.hasAllModules(['Module1', 'Module3'])).toBeFalsy()
    })

    test('own none of the modules', () => {
        expect(abilityBuilder.hasAllModules(['Module3'])).toBeFalsy()
    })
})
