import { createAppLocalVue } from '@/../tests/utils' // should be the first to implement default mocks
import App from '@/app/App.vue'
import { mount, shallowMount } from '@vue/test-utils'
import { DASHBOARD_HOME } from '@/config/url-prefix'
import { loggedInStore, loggedOutStore } from '@/../tests/factories/auth-store'
import flushPromises from 'flush-promises'
import Vuex from 'vuex'
import Vue from 'vue'
import security, { ACTION_ALLOW, ROLE_IS_LOGGED_IN } from '@/plugins/security'
import {
  IS_LOGGED_IN_GETTER_NAME,
  LOG_IN_MUTATION_TYPE,
  LOG_OUT_MUTATION_TYPE,
  LOGIN_URL,
  MEMBER_AUTHORITY_GETTER,
} from '@/config/defaults'
import { store as appStore } from '@/store'
import Router from 'vue-router'

describe('Security', () => {
  let router
  let SecurityConfig
  let plugins

  beforeEach(async () => {
    jest.resetModules()
    jest.clearAllMocks()

    router = await require('@/router').default

    SecurityConfig = await require('@/config/security').default

    plugins = [
      SecurityConfig.registerStorePlugin(
        LOG_IN_MUTATION_TYPE,
        LOG_OUT_MUTATION_TYPE,
        IS_LOGGED_IN_GETTER_NAME,
        MEMBER_AUTHORITY_GETTER
      ),
    ]

    await flushPromises()
  })

  describe('Security - Policy', function() {
    it('should subscribe to login mutation', async () => {
      Vue.use(Vuex)
      let store = new Vuex.Store({
        plugins,
      })

      expect(SecurityConfig._store).toBe(store)
      expect(SecurityConfig.loggedInGetterName).toBe(IS_LOGGED_IN_GETTER_NAME)
      expect(SecurityConfig.logInMutationType).toBe(LOG_IN_MUTATION_TYPE)
      expect(SecurityConfig.logOutMutationType).toBe(LOG_OUT_MUTATION_TYPE)
      expect(SecurityConfig.memberAuthorityGetterName).toBe(MEMBER_AUTHORITY_GETTER)
      expect(store._subscribers).toHaveLength(1)
    })

    it('should check policy for view access by default and allow if exists', async () => {
      Vue.use(Vuex)
      let store = new Vuex.Store({
        state: {
          memberAuthority: {
            dashboard: ['view'],
          },
        },
        getters: {
          [MEMBER_AUTHORITY_GETTER]: state => {
            return state.memberAuthority
          },
        },
        plugins,
      })

      expect(
        SecurityConfig.policy.check({
          policy: { dashboard: 'view' },
        })
      ).toBeTruthy()
    })

    it('should check policy for view access by default and deny if dsnt', async () => {
      Vue.use(Vuex)
      let store = new Vuex.Store({
        state: {
          memberAuthority: {},
        },
        getters: {
          [MEMBER_AUTHORITY_GETTER]: state => {
            return state.memberAuthority
          },
        },
        plugins,
      })

      expect(
        SecurityConfig.policy.check({
          policy: { dashboard: 'view' },
        })
      ).toBeFalsy()
    })
  })

  describe('Security - Can Directive', function() {
    let localVue

    beforeEach(() => {
      localVue = createAppLocalVue()
    })

    it('should not display element if v-can cannot validate', async () => {
      localVue.use(Vuex)
      localVue.use(security)

      let store = new Vuex.Store({
        state: {
          memberAuthority: {},
        },
        getters: {
          [MEMBER_AUTHORITY_GETTER]: state => {
            return state.memberAuthority
          },
        },
        plugins,
      })

      let TestComponent = Vue.component('test', {
        template: `
        <div>
          <div class="dashboard-ba" v-can="\`dashboard\`">test bind</div>
        </div>`,
      })
      const wrapper = mount(TestComponent, {
        localVue,
        store,
      })

      await flushPromises()

      expect(wrapper.element).toMatchSnapshot()

      expect(wrapper.element.innerHTML).toBe('')
      expect(wrapper.vm.permissions.dashboard.create).toBe(false)
      expect(wrapper.vm.permissions.dashboard.view).toBe(false)
      expect(wrapper.vm.permissions.dashboard.edit).toBe(false)
      expect(wrapper.vm.permissions.dashboard.list).toBe(false)
      expect(wrapper.vm.permissions.dashboard.search).toBe(false)
      expect(wrapper.vm.permissions.dashboard.delete).toBe(false)
    })

    it('should display element if v-can validate', async () => {
      localVue.use(Vuex)
      localVue.use(security)

      let store = new Vuex.Store({
        state: {
          memberAuthority: {
            dashboard: ['create', 'view', 'edit', 'list', 'search', 'delete'],
          },
        },
        getters: {
          [MEMBER_AUTHORITY_GETTER]: state => {
            return state.memberAuthority
          },
        },
        plugins,
      })

      let TestComponent = Vue.component('test', {
        template: `
        <div>
          <div class="dashboard-ba" v-can="\`dashboard\`">test bind</div>
        </div>`,
      })
      const wrapper = mount(TestComponent, {
        localVue,
        store,
      })

      await flushPromises()

      expect(wrapper.element).toMatchSnapshot()

      expect(wrapper.element.innerHTML).toContain('test bind')
      expect(wrapper.vm.permissions.dashboard.create).toBe(true)
      expect(wrapper.vm.permissions.dashboard.view).toBe(true)
      expect(wrapper.vm.permissions.dashboard.edit).toBe(true)
      expect(wrapper.vm.permissions.dashboard.list).toBe(true)
      expect(wrapper.vm.permissions.dashboard.search).toBe(true)
      expect(wrapper.vm.permissions.dashboard.delete).toBe(true)
    })

    it('should not display element if v-can cannot validate list', async () => {
      localVue.use(Vuex)
      localVue.use(security)

      let store = new Vuex.Store({
        modules: {
          permission: {
            state: {
              memberAuthority: {},
            },
            getters: {
              [MEMBER_AUTHORITY_GETTER]: state => {
                return state.memberAuthority
              },
            },
          },
        },
        plugins,
      })

      let TestComponent = Vue.component('test', {
        template: `
        <div>
          <div class="dashboard-ba" v-can:list="\`dashboard\`">test bind</div>
        </div>`,
      })
      const wrapper = mount(TestComponent, {
        localVue,
        store,
      })

      await flushPromises()

      expect(wrapper.element).toMatchSnapshot()

      expect(wrapper.element.innerHTML).toBe('')
      expect(wrapper.vm.permissions.dashboard.create).toBe(false)
      expect(wrapper.vm.permissions.dashboard.view).toBe(false)
      expect(wrapper.vm.permissions.dashboard.edit).toBe(false)
      expect(wrapper.vm.permissions.dashboard.list).toBe(false)
      expect(wrapper.vm.permissions.dashboard.search).toBe(false)
      expect(wrapper.vm.permissions.dashboard.delete).toBe(false)
    })

    it('should display element if v-can validate list', async () => {
      localVue.use(Vuex)
      localVue.use(security)

      let store = new Vuex.Store({
        modules: {
          permission: {
            state: {
              memberAuthority: {
                dashboard: ['list'],
              },
            },
            getters: {
              [MEMBER_AUTHORITY_GETTER]: state => {
                return state.memberAuthority
              },
            },
          },
        },
        plugins,
      })

      let TestComponent = Vue.component('test', {
        template: `
        <div>
          <div class="dashboard-ba" v-can:list="\`dashboard\`">test bind</div>
        </div>`,
      })
      const wrapper = mount(TestComponent, {
        localVue,
        store,
      })

      await flushPromises()

      expect(wrapper.element).toMatchSnapshot()

      expect(wrapper.element.innerHTML).toContain('test bind')
      expect(wrapper.vm.permissions.dashboard.create).toBe(false)
      expect(wrapper.vm.permissions.dashboard.view).toBe(false)
      expect(wrapper.vm.permissions.dashboard.edit).toBe(false)
      expect(wrapper.vm.permissions.dashboard.list).toBe(true)
      expect(wrapper.vm.permissions.dashboard.search).toBe(false)
      expect(wrapper.vm.permissions.dashboard.delete).toBe(false)
    })
  })

  describe('Security - Routes & Policy', function() {
    let localVue
    let access

    beforeEach(() => {
      SecurityConfig.routes.rules([
        { path: '^/*', role: ROLE_IS_LOGGED_IN, action: ACTION_ALLOW, to: { path: LOGIN_URL } },
      ])

      localVue = createAppLocalVue()
      router = new Router({
        routes: [
          {
            path: '/',
          },
          {
            path: '/policy',
            meta: {
              policy: {
                dashboard: 'view',
              },
            },
          },
        ],
      })
      router.beforeEach(to => {
        access = SecurityConfig.check(to)
      })
    })

    it('should not route if route require loggin in but not logged in', async () => {
      let store = loggedOutStore()
      const wrapper = shallowMount(App, {
        localVue,
        router,
        store: store,
      })

      router.push('/')

      await flushPromises()

      expect(access).not.toBe(true)
    })

    it('should route if route require loggin and is logged in', async () => {
      let store = loggedInStore()
      const wrapper = shallowMount(App, {
        localVue,
        router,
        store: store,
      })

      router.push('/')

      await flushPromises()

      expect(access).toBe(true)
    })

    it(`should not route if route require loggin and policy, 
         user is logged in but policy did not match`, async () => {
      let store = loggedInStore({
        modules: {
          permission: {
            state: {
              memberAuthority: {},
            },
            getters: {
              [MEMBER_AUTHORITY_GETTER]: state => {
                return state.memberAuthority
              },
            },
          },
        },
        plugins,
      })

      const wrapper = shallowMount(App, {
        localVue,
        router,
        store: store,
      })

      router.push('/policy')

      await flushPromises()

      expect(access).not.toBe(true)
    })

    it(`should route if route require loggin and policy, 
         user is logged in and policy match`, async () => {
      let store = loggedInStore({
        modules: {
          permission: {
            state: {
              memberAuthority: {
                dashboard: ['view'],
              },
            },
            getters: {
              [MEMBER_AUTHORITY_GETTER]: state => {
                return state.memberAuthority
              },
            },
          },
        },
        plugins,
      })

      const wrapper = shallowMount(App, {
        localVue,
        router,
        store: store,
      })

      router.push('/policy')

      await flushPromises()

      expect(access).toBe(true)
    })
  })
})
