import { describe, it, expect } from 'vitest'
import { Hono } from 'hono'
import { newEnforcer } from 'casbin'
import { casbin } from '../src'
import { basicAuthorizer, jwtAuthorizer } from '../src/helper'
import { jwt, sign } from 'hono/jwt'
import { basicAuth } from 'hono/basic-auth'

describe('Casbin Middleware Tests', () => {
  describe('BasicAuthorizer', () => {
    const app = new Hono()
    const enforcer = newEnforcer('examples/model.conf', 'examples/policy.csv')
    app.use('*', casbin({ newEnforcer: enforcer, authorizer: basicAuthorizer }))
    app.get('/dataset1/test', (c) => c.text('dataset1 test'))
    app.post('/dataset1/test', (c) => c.text('dataset1 test'))
    app.put('/dataset1/test', (c) => c.text('dataset1 test'))
    app.delete('/dataset1/test', (c) => c.text('dataset1 test'))

    it('test[Success]: p, alice, /dataset1/*, GET', async () => {
      const req = new Request('http://localhost/dataset1/test', {
        method: 'GET',
        headers: {
          Authorization: `Basic ${Buffer.from('alice:password').toString('base64')}`,
        },
      })
      const res = await app.fetch(req)
      expect(res.status).toBe(200)
    })

    it('test[Success]: p, alice, /dataset1/*, POST', async () => {
      const req = new Request('http://localhost/dataset1/test', {
        method: 'POST',
        headers: {
          Authorization: `Basic ${Buffer.from('alice:password').toString('base64')}`,
        },
      })
      const res = await app.fetch(req)
      expect(res.status).toBe(200)
    })

    it('test[Success]: p, alice, /dataset1/*, PUT', async () => {
      const req = new Request('http://localhost/dataset1/test', {
        method: 'PUT',
        headers: {
          Authorization: `Basic ${Buffer.from('alice:password').toString('base64')}`,
        },
      })
      const res = await app.fetch(req)
      expect(res.status).toBe(200)
    })

    it('test[Success]: p, alice, /dataset1/*, DELETE', async () => {
      const req = new Request('http://localhost/dataset1/test', {
        method: 'DELETE',
        headers: {
          Authorization: `Basic ${Buffer.from('alice:password').toString('base64')}`,
        },
      })
      const res = await app.fetch(req)
      expect(res.status).toBe(200)
    })

    it('test[Not Exist User]: p, cathy, /dataset1/*, - GET 403', async () => {
      const req = new Request('http://localhost/dataset1/test', {
        method: 'GET',
        headers: {
          Authorization: `Basic ${Buffer.from('cathy:password').toString('base64')}`,
        },
      })
      const res = await app.fetch(req)
      expect(res.status).toBe(403)
    })

    it('test[Not Exist User]: p, cathy, /dataset1/*, - POST 403', async () => {
      const req = new Request('http://localhost/dataset1/test', {
        method: 'POST',
        headers: {
          Authorization: `Basic ${Buffer.from('cathy:password').toString('base64')}`,
        },
      })
      const res = await app.fetch(req)
      expect(res.status).toBe(403)
    })

    it('test[Not Exist User]: p, cathy, /dataset1/*, - PUT 403', async () => {
      const req = new Request('http://localhost/dataset1/test', {
        method: 'PUT',
        headers: {
          Authorization: `Basic ${Buffer.from('cathy:password').toString('base64')}`,
        },
      })
      const res = await app.fetch(req)
      expect(res.status).toBe(403)
    })

    it('test[Not Exist User]: p, cathy, /dataset1/*, - DELETE 403', async () => {
      const req = new Request('http://localhost/dataset1/test', {
        method: 'DELETE',
        headers: {
          Authorization: `Basic ${Buffer.from('cathy:password').toString('base64')}`,
        },
      })
      const res = await app.fetch(req)
      expect(res.status).toBe(403)
    })

    it('test[Insufficient Permissions]: p, bob, /dataset1/*, - GET 403', async () => {
      const req = new Request('http://localhost/dataset1/test', {
        method: 'GET',
        headers: {
          Authorization: `Basic ${Buffer.from('bob:password').toString('base64')}`,
        },
      })
      const res = await app.fetch(req)
      expect(res.status).toBe(403)
    })

    it('test[Insufficient Permissions]: p, bob, /dataset1/*, - POST 403', async () => {
      const req = new Request('http://localhost/dataset1/test', {
        method: 'POST',
        headers: {
          Authorization: `Basic ${Buffer.from('bob:password').toString('base64')}`,
        },
      })
      const res = await app.fetch(req)
      expect(res.status).toBe(403)
    })

    it('test[Insufficient Permissions]: p, bob, /dataset1/*, - PUT 403', async () => {
      const req = new Request('http://localhost/dataset1/test', {
        method: 'PUT',
        headers: {
          Authorization: `Basic ${Buffer.from('bob:password').toString('base64')}`,
        },
      })
      const res = await app.fetch(req)
      expect(res.status).toBe(403)
    })

    it('test[Insufficient Permissions]: p, bob, /dataset1/*, - DELETE 403', async () => {
      const req = new Request('http://localhost/dataset1/test', {
        method: 'DELETE',
        headers: {
          Authorization: `Basic ${Buffer.from('bob:password').toString('base64')}`,
        },
      })
      const res = await app.fetch(req)
      expect(res.status).toBe(403)
    })
  })

  describe('BasicAuthorizer with hono/basic-auth', () => {
    const app = new Hono()
    const enforcer = newEnforcer('examples/model.conf', 'examples/policy.csv')
    app.use(
      '*',
      basicAuth(
        {
          username: 'alice',
          password: 'password',
        },
        {
          username: 'bob',
          password: 'password',
        }
      ),
      casbin({ newEnforcer: enforcer, authorizer: basicAuthorizer })
    )
    app.get('/dataset1/test', (c) => c.text('dataset1 test'))
    app.post('/dataset1/test', (c) => c.text('dataset1 test'))
    app.put('/dataset1/test', (c) => c.text('dataset1 test'))
    app.delete('/dataset1/test', (c) => c.text('dataset1 test'))

    it('test[Success]: p, alice, /dataset1/*, GET', async () => {
      const req = new Request('http://localhost/dataset1/test', {
        method: 'GET',
        headers: {
          Authorization: `Basic ${Buffer.from('alice:password').toString('base64')}`,
        },
      })
      const res = await app.fetch(req)
      expect(res.status).toBe(200)
    })

    it('test[Success]: p, alice, /dataset1/*, POST', async () => {
      const req = new Request('http://localhost/dataset1/test', {
        method: 'POST',
        headers: {
          Authorization: `Basic ${Buffer.from('alice:password').toString('base64')}`,
        },
      })
      const res = await app.fetch(req)
      expect(res.status).toBe(200)
    })

    it('test[Success]: p, alice, /dataset1/*, PUT', async () => {
      const req = new Request('http://localhost/dataset1/test', {
        method: 'PUT',
        headers: {
          Authorization: `Basic ${Buffer.from('alice:password').toString('base64')}`,
        },
      })
      const res = await app.fetch(req)
      expect(res.status).toBe(200)
    })

    it('test[Success]: p, alice, /dataset1/*, DELETE', async () => {
      const req = new Request('http://localhost/dataset1/test', {
        method: 'DELETE',
        headers: {
          Authorization: `Basic ${Buffer.from('alice:password').toString('base64')}`,
        },
      })
      const res = await app.fetch(req)
      expect(res.status).toBe(200)
    })

    it('test[Insufficient Permissions]: p, bob, /dataset1/*, - GET 403', async () => {
      const req = new Request('http://localhost/dataset1/test', {
        method: 'GET',
        headers: {
          Authorization: `Basic ${Buffer.from('bob:password').toString('base64')}`,
        },
      })
      const res = await app.fetch(req)
      expect(res.status).toBe(403)
    })

    it('test[Insufficient Permissions]: p, bob, /dataset1/*, - POST 403', async () => {
      const req = new Request('http://localhost/dataset1/test', {
        method: 'POST',
        headers: {
          Authorization: `Basic ${Buffer.from('bob:password').toString('base64')}`,
        },
      })
      const res = await app.fetch(req)
      expect(res.status).toBe(403)
    })

    it('test[Insufficient Permissions]: p, bob, /dataset1/*, - PUT 403', async () => {
      const req = new Request('http://localhost/dataset1/test', {
        method: 'PUT',
        headers: {
          Authorization: `Basic ${Buffer.from('bob:password').toString('base64')}`,
        },
      })
      const res = await app.fetch(req)
      expect(res.status).toBe(403)
    })

    it('test[Insufficient Permissions]: p, bob, /dataset1/*, - DELETE 403', async () => {
      const req = new Request('http://localhost/dataset1/test', {
        method: 'DELETE',
        headers: {
          Authorization: `Basic ${Buffer.from('bob:password').toString('base64')}`,
        },
      })
      const res = await app.fetch(req)
      expect(res.status).toBe(403)
    })
  })

  describe('JWTAuthorizer', () => {
    const app = new Hono()
    const enforcer = newEnforcer('examples/model.conf', 'examples/policy.csv')

    app.use('*', casbin({ newEnforcer: enforcer, authorizer: jwtAuthorizer }))
    app.get('/dataset1/test', (c) => c.text('dataset1 test'))
    app.post('/dataset1/test', (c) => c.text('dataset1 test'))
    app.put('/dataset1/test', (c) => c.text('dataset1 test'))
    app.delete('/dataset1/test', (c) => c.text('dataset1 test'))

    it('test[Success]: p, alice, /dataset1/*, GET 200', async () => {
      const token = await sign({ sub: 'alice' }, 'secret')
      const req = new Request('http://localhost/dataset1/test', {
        method: 'GET',
        headers: {
          Authorization: `Bearer ${token}`,
        },
      })
      const res = await app.fetch(req)
      expect(res.status).toBe(200)
    })

    it('test[Success]: p, alice, /dataset1/*, POST 200', async () => {
      const token = await sign({ sub: 'alice' }, 'secret')
      const req = new Request('http://localhost/dataset1/test', {
        method: 'POST',
        headers: {
          Authorization: `Bearer ${token}`,
        },
      })
      const res = await app.fetch(req)
      expect(res.status).toBe(200)
    })

    it('test[Success]: p, alice, /dataset1/*, PUT 200', async () => {
      const token = await sign({ sub: 'alice' }, 'secret')
      const req = new Request('http://localhost/dataset1/test', {
        method: 'PUT',
        headers: {
          Authorization: `Bearer ${token}`,
        },
      })
      const res = await app.fetch(req)
      expect(res.status).toBe(200)
    })

    it('test[Success]: p, alice, /dataset1/*, DELETE 200', async () => {
      const token = await sign({ sub: 'alice' }, 'secret')
      const req = new Request('http://localhost/dataset1/test', {
        method: 'DELETE',
        headers: {
          Authorization: `Bearer ${token}`,
        },
      })
      const res = await app.fetch(req)
      expect(res.status).toBe(200)
    })

    it('test[Not Exist User]: p & g, dataset1_admin, /dataset1/*, * - GET 403', async () => {
      const token = await sign({ sub: 'cathy' }, 'secret')
      const req = new Request('http://localhost/dataset1/test', {
        method: 'GET',
        headers: {
          Authorization: `Bearer ${token}`,
        },
      })
      const res = await app.fetch(req)
      expect(res.status).toBe(403)
    })

    it('test[Not Exist User]: p & g, dataset1_admin, /dataset1/*, * - POST 403', async () => {
      const token = await sign({ sub: 'cathy' }, 'secret')
      const req = new Request('http://localhost/dataset1/test', {
        method: 'POST',
        headers: {
          Authorization: `Bearer ${token}`,
        },
      })
      const res = await app.fetch(req)
      expect(res.status).toBe(403)
    })

    it('test[Not Exist User]: p & g, dataset1_admin, /dataset1/*, * - PUT 403', async () => {
      const token = await sign({ sub: 'cathy' }, 'secret')
      const req = new Request('http://localhost/dataset1/test', {
        method: 'PUT',
        headers: {
          Authorization: `Bearer ${token}`,
        },
      })
      const res = await app.fetch(req)
      expect(res.status).toBe(403)
    })

    it('test[Not Exist User]: p & g, dataset1_admin, /dataset1/*, * - DELETE 403', async () => {
      const token = await sign({ sub: 'cathy' }, 'secret')
      const req = new Request('http://localhost/dataset1/test', {
        method: 'DELETE',
        headers: {
          Authorization: `Bearer ${token}`,
        },
      })
      const res = await app.fetch(req)
      expect(res.status).toBe(403)
    })

    it('test[Insufficient Permissions]: p, bob, /dataset1/test - GET 403', async () => {
      const token = await sign({ sub: 'bob' }, 'secret')
      const req = new Request('http://localhost/dataset1/test', {
        method: 'GET',
        headers: {
          Authorization: `Bearer ${token}`,
        },
      })
      const res = await app.fetch(req)
      expect(res.status).toBe(403)
    })

    it('test[Insufficient Permissions]: p, bob, /dataset1/test - POST 403', async () => {
      const token = await sign({ sub: 'bob' }, 'secret')
      const req = new Request('http://localhost/dataset1/test', {
        method: 'POST',
        headers: {
          Authorization: `Bearer ${token}`,
        },
      })
      const res = await app.fetch(req)
      expect(res.status).toBe(403)
    })

    it('test[Insufficient Permissions]: p, bob, /dataset1/test - PUT 403', async () => {
      const token = await sign({ sub: 'bob' }, 'secret')
      const req = new Request('http://localhost/dataset1/test', {
        method: 'PUT',
        headers: {
          Authorization: `Bearer ${token}`,
        },
      })
      const res = await app.fetch(req)
      expect(res.status).toBe(403)
    })

    it('test[Insufficient Permissions]: p, bob, /dataset1/test - DELETE 403', async () => {
      const token = await sign({ sub: 'bob' }, 'secret')
      const req = new Request('http://localhost/dataset1/test', {
        method: 'DELETE',
        headers: {
          Authorization: `Bearer ${token}`,
        },
      })
      const res = await app.fetch(req)
      expect(res.status).toBe(403)
    })
  })

  describe('JWTAuthorizer With Custom Claim', () => {
    const app = new Hono()
    const enforcer = newEnforcer('examples/model.conf', 'examples/policy.csv')
    const customClaimMapping = { userID: 'custom_id' }
    app.use(
      '*',
      casbin({
        newEnforcer: enforcer,
        authorizer: (c, e) => jwtAuthorizer(c, e, customClaimMapping),
      })
    )
    app.get('/dataset1/test', (c) => c.text('dataset1 test'))
    app.post('/dataset1/test', (c) => c.text('dataset1 test'))
    app.put('/dataset1/test', (c) => c.text('dataset1 test'))
    app.delete('/dataset1/test', (c) => c.text('dataset1 test'))

    it('test[Success]: p, alice, /dataset1/*, GET 200', async () => {
      const token = await sign({ custom_id: 'alice' }, 'secret')
      const req = new Request('http://localhost/dataset1/test', {
        method: 'GET',
        headers: {
          Authorization: `Bearer ${token}`,
        },
      })
      const res = await app.fetch(req)
      expect(res.status).toBe(200)
    })

    it('test[Success]: p, alice, /dataset1/*, POST 200', async () => {
      const token = await sign({ custom_id: 'alice' }, 'secret')
      const req = new Request('http://localhost/dataset1/test', {
        method: 'POST',
        headers: {
          Authorization: `Bearer ${token}`,
        },
      })
      const res = await app.fetch(req)
      expect(res.status).toBe(200)
    })

    it('test[Success]: p, alice, /dataset1/*, PUT 200', async () => {
      const token = await sign({ custom_id: 'alice' }, 'secret')
      const req = new Request('http://localhost/dataset1/test', {
        method: 'PUT',
        headers: {
          Authorization: `Bearer ${token}`,
        },
      })
      const res = await app.fetch(req)
      expect(res.status).toBe(200)
    })

    it('test[Success]: p, alice, /dataset1/*, DELETE 200', async () => {
      const token = await sign({ custom_id: 'alice' }, 'secret')
      const req = new Request('http://localhost/dataset1/test', {
        method: 'DELETE',
        headers: {
          Authorization: `Bearer ${token}`,
        },
      })
      const res = await app.fetch(req)
      expect(res.status).toBe(200)
    })

    it('test[Not Exist User]: p & g, dataset1_admin, /dataset1/*, * - GET 403', async () => {
      const token = await sign({ custom_id: 'cathy' }, 'secret')
      const req = new Request('http://localhost/dataset1/test', {
        method: 'GET',
        headers: {
          Authorization: `Bearer ${token}`,
        },
      })
      const res = await app.fetch(req)
      expect(res.status).toBe(403)
    })

    it('test[Not Exist User]: p & g, dataset1_admin, /dataset1/*, * - POST 403', async () => {
      const token = await sign({ custom_id: 'cathy' }, 'secret')
      const req = new Request('http://localhost/dataset1/test', {
        method: 'POST',
        headers: {
          Authorization: `Bearer ${token}`,
        },
      })
      const res = await app.fetch(req)
      expect(res.status).toBe(403)
    })

    it('test[Not Exist User]: p & g, dataset1_admin, /dataset1/*, * - PUT 403', async () => {
      const token = await sign({ custom_id: 'cathy' }, 'secret')
      const req = new Request('http://localhost/dataset1/test', {
        method: 'PUT',
        headers: {
          Authorization: `Bearer ${token}`,
        },
      })
      const res = await app.fetch(req)
      expect(res.status).toBe(403)
    })

    it('test[Not Exist User]: p & g, dataset1_admin, /dataset1/*, * - DELETE 403', async () => {
      const token = await sign({ custom_id: 'cathy' }, 'secret')
      const req = new Request('http://localhost/dataset1/test', {
        method: 'DELETE',
        headers: {
          Authorization: `Bearer ${token}`,
        },
      })
      const res = await app.fetch(req)
      expect(res.status).toBe(403)
    })

    it('test[Insufficient Permissions]: p, bob, /dataset1/test - GET 403', async () => {
      const token = await sign({ custom_id: 'bob' }, 'secret')
      const req = new Request('http://localhost/dataset1/test', {
        method: 'GET',
        headers: {
          Authorization: `Bearer ${token}`,
        },
      })
      const res = await app.fetch(req)
      expect(res.status).toBe(403)
    })

    it('test[Insufficient Permissions]: p, bob, /dataset1/test - POST 403', async () => {
      const token = await sign({ custom_id: 'bob' }, 'secret')
      const req = new Request('http://localhost/dataset1/test', {
        method: 'POST',
        headers: {
          Authorization: `Bearer ${token}`,
        },
      })
      const res = await app.fetch(req)
      expect(res.status).toBe(403)
    })

    it('test[Insufficient Permissions]: p, bob, /dataset1/test - PUT 403', async () => {
      const token = await sign({ custom_id: 'bob' }, 'secret')
      const req = new Request('http://localhost/dataset1/test', {
        method: 'PUT',
        headers: {
          Authorization: `Bearer ${token}`,
        },
      })
      const res = await app.fetch(req)
      expect(res.status).toBe(403)
    })

    it('test[Insufficient Permissions]: p, bob, /dataset1/test - DELETE 403', async () => {
      const token = await sign({ custom_id: 'bob' }, 'secret')
      const req = new Request('http://localhost/dataset1/test', {
        method: 'DELETE',
        headers: {
          Authorization: `Bearer ${token}`,
        },
      })
      const res = await app.fetch(req)
      expect(res.status).toBe(403)
    })
  })

  describe('JWTAuthorizer With hono/jwt', () => {
    const app = new Hono()
    const enforcer = newEnforcer('examples/model.conf', 'examples/policy.csv')
    app.use(
      '*',
      jwt({
        secret: 'secret',
      }),
      casbin({ newEnforcer: enforcer, authorizer: jwtAuthorizer })
    )
    app.get('/dataset1/test', (c) => c.text('dataset1 test'))
    app.post('/dataset1/test', (c) => c.text('dataset1 test'))
    app.put('/dataset1/test', (c) => c.text('dataset1 test'))
    app.delete('/dataset1/test', (c) => c.text('dataset1 test'))

    it('test[Success]: p, alice, /dataset1/*, GET 200', async () => {
      const token = await sign({ sub: 'alice' }, 'secret')
      const req = new Request('http://localhost/dataset1/test', {
        method: 'GET',
        headers: {
          Authorization: `Bearer ${token}`,
        },
      })
      const res = await app.fetch(req)
      expect(res.status).toBe(200)
    })

    it('test[Success]: p, alice, /dataset1/*, POST 200', async () => {
      const token = await sign({ sub: 'alice' }, 'secret')
      const req = new Request('http://localhost/dataset1/test', {
        method: 'POST',
        headers: {
          Authorization: `Bearer ${token}`,
        },
      })
      const res = await app.fetch(req)
      expect(res.status).toBe(200)
    })

    it('test[Success]: p, alice, /dataset1/*, PUT 200', async () => {
      const token = await sign({ sub: 'alice' }, 'secret')
      const req = new Request('http://localhost/dataset1/test', {
        method: 'PUT',
        headers: {
          Authorization: `Bearer ${token}`,
        },
      })
      const res = await app.fetch(req)
      expect(res.status).toBe(200)
    })

    it('test[Success]: p, alice, /dataset1/*, DELETE 200', async () => {
      const token = await sign({ sub: 'alice' }, 'secret')
      const req = new Request('http://localhost/dataset1/test', {
        method: 'DELETE',
        headers: {
          Authorization: `Bearer ${token}`,
        },
      })
      const res = await app.fetch(req)
      expect(res.status).toBe(200)
    })

    it('test[Not Exist User]: p & g, dataset1_admin, /dataset1/*, * - GET 403', async () => {
      const token = await sign({ sub: 'cathy' }, 'secret')
      const req = new Request('http://localhost/dataset1/test', {
        method: 'GET',
        headers: {
          Authorization: `Bearer ${token}`,
        },
      })
      const res = await app.fetch(req)
      expect(res.status).toBe(403)
    })

    it('test[Not Exist User]: p & g, dataset1_admin, /dataset1/*, * - POST 403', async () => {
      const token = await sign({ sub: 'cathy' }, 'secret')
      const req = new Request('http://localhost/dataset1/test', {
        method: 'POST',
        headers: {
          Authorization: `Bearer ${token}`,
        },
      })
      const res = await app.fetch(req)
      expect(res.status).toBe(403)
    })

    it('test[Not Exist User]: p & g, dataset1_admin, /dataset1/*, * - PUT 403', async () => {
      const token = await sign({ sub: 'cathy' }, 'secret')
      const req = new Request('http://localhost/dataset1/test', {
        method: 'PUT',
        headers: {
          Authorization: `Bearer ${token}`,
        },
      })
      const res = await app.fetch(req)
      expect(res.status).toBe(403)
    })

    it('test[Not Exist User]: p & g, dataset1_admin, /dataset1/*, * - DELETE 403', async () => {
      const token = await sign({ sub: 'cathy' }, 'secret')
      const req = new Request('http://localhost/dataset1/test', {
        method: 'DELETE',
        headers: {
          Authorization: `Bearer ${token}`,
        },
      })
      const res = await app.fetch(req)
      expect(res.status).toBe(403)
    })

    it('test[Insufficient Permissions]: p, bob, /dataset1/test - GET 403', async () => {
      const token = await sign({ sub: 'bob' }, 'secret')
      const req = new Request('http://localhost/dataset1/test', {
        method: 'GET',
        headers: {
          Authorization: `Bearer ${token}`,
        },
      })
      const res = await app.fetch(req)
      expect(res.status).toBe(403)
    })

    it('test[Insufficient Permissions]: p, bob, /dataset1/test - POST 403', async () => {
      const token = await sign({ sub: 'bob' }, 'secret')
      const req = new Request('http://localhost/dataset1/test', {
        method: 'POST',
        headers: {
          Authorization: `Bearer ${token}`,
        },
      })
      const res = await app.fetch(req)
      expect(res.status).toBe(403)
    })

    it('test[Insufficient Permissions]: p, bob, /dataset1/test - PUT 403', async () => {
      const token = await sign({ sub: 'bob' }, 'secret')
      const req = new Request('http://localhost/dataset1/test', {
        method: 'PUT',
        headers: {
          Authorization: `Bearer ${token}`,
        },
      })
      const res = await app.fetch(req)
      expect(res.status).toBe(403)
    })

    it('test[Insufficient Permissions]: p, bob, /dataset1/test - DELETE 403', async () => {
      const token = await sign({ sub: 'bob' }, 'secret')
      const req = new Request('http://localhost/dataset1/test', {
        method: 'DELETE',
        headers: {
          Authorization: `Bearer ${token}`,
        },
      })
      const res = await app.fetch(req)
      expect(res.status).toBe(403)
    })
  })
})
