import { Eltro as t, assert} from 'eltro'
import { HttpError } from 'flaska'

import { createContext } from '../helper.server.mjs'
import { verifyToken, verifyBody, throwIfNotPublic } from '../../api/media/security.mjs'
import encode from '../../api/jwt/encode.mjs'
import config from '../../api/config.mjs'

t.describe('#throwIfNotPublic()', function() {
  let backup = {}

  t.before(function() {
    backup = config.sources[1].store
    config.sources[1].store = {
      sites: {
        justatest: {
        },
        justatest2: {
          public: false,
        },
        justatest3: {
          public: true,
        },
      },
    }
  })

  t.after(function() {
    config.sources[1].store = backup
  })

  t.test('should throw for sites that do not exist or are null', function() {
    let tests = [
      'justatest',
      'justatest2',
      'nonexisting1',
      null,
    ]

    tests.forEach(function(test) {
      assert.throws(function() { throwIfNotPublic(test) }, function(err) {
        assert.ok(err instanceof HttpError)
        assert.ok(err instanceof Error)
        assert.strictEqual(err.status, 404)
        assert.match(err.message, new RegExp(test))
        assert.match(err.message, /exist/i)
        return true
      }, `should throw with site ${test}`)
    })
  })
  

  t.test('should pass for sites that allow public listing', function() {
    let tests = [
      'justatest3',
    ]

    tests.forEach(function(test) {
      assert.doesNotThrow(function() { throwIfNotPublic(test) }, `should not throw with site ${test}`)
    })
  })
})

t.describe('#verifyToken()', function() {
  let backup = {}
  t.before(function() {
    backup = config.sources[1].store
    config.sources[1].store = {
      sites: {
        justatest: {
          keys: {
            'default@HS512': 'mysharedkey',
          }
        },
      },
    }
  })

  t.after(function() {
    config.sources[1].store = backup
  })

  t.test('should fail if query token is missing', function() {
    let ctx = createContext({ })
    ctx.query.delete('token')

    assert.throws(function() { verifyToken(ctx) }, function(err) {
      assert.ok(err instanceof HttpError)
      assert.ok(err instanceof Error)
      assert.strictEqual(err.status, 422)
      assert.match(err.message, /query/i)
      assert.match(err.message, /token/i)
      return true
    })
  })

  function assertInvalidToken(err) {
    assert.ok(err instanceof HttpError)
    assert.ok(err instanceof Error)
    assert.strictEqual(err.status, 422)
    assert.match(err.message, /invalid/i)
    assert.match(err.message, /token/i)
    return true
  }

  t.test('should fail if token is invalid', function() {
    let ctx = createContext({ })
    ctx.query.set('token', 'asdfasdgassdga')

    assert.throws(function() { verifyToken(ctx) }, assertInvalidToken)
    assert.ok(ctx.log.error.lastCall)
    assert.match(ctx.log.error.lastCall[0].message, /3 dots/)

    ctx.query.set('token', 'asdfasdgassdga.asdfasdg.sadfsadfas')

    assert.throws(function() { verifyToken(ctx) }, assertInvalidToken)
    assert.match(ctx.log.error.lastCall[0].message, /invalid/i)
    
    ctx.query.set('token', encode(
      { typ: 'JWT', alg: 'HS256' },
      { iss: 'justatest' },
      'mysharedkey'
    ))
    assert.throws(function() { verifyToken(ctx) }, assertInvalidToken)
    assert.match(ctx.log.error.lastCall[0].message, /pubkey/)

    ctx.query.set('token', encode(
      { typ: 'JWT', alg: 'HS512' },
      { iss: 'notexist' },
      'mysharedkey'
    ))
    assert.throws(function() { verifyToken(ctx) }, assertInvalidToken)
    assert.match(ctx.log.error.lastCall[0].message, /notexist/)

    ctx.query.set('token', encode(
      { typ: 'JWT', alg: 'HS512' },
      { iss: 'justatest' },
      'mysharedkey2'
    ))
    assert.throws(function() { verifyToken(ctx) }, assertInvalidToken)
    assert.match(ctx.log.error.lastCall[0].message, /HS512/)
    assert.match(ctx.log.error.lastCall[0].message, /Verification/i)
  })

  t.test('should otherwise return the issuer', function() {
    let ctx = createContext({ })
    ctx.query.set('token', encode(
      { typ: 'JWT', alg: 'HS512' },
      { iss: 'justatest' },
      'mysharedkey'
    ))
    let site = verifyToken(ctx)
    assert.strictEqual(site, 'justatest')
  })
})

t.describe('#verifyBody()', function() {
  t.test('should succeed with empty body', function() {
    let ctx = createContext({ req: { body: { } } })

    verifyBody(ctx)
  })
  
  t.test('should fail with invalid body', function() {
    let ctx = createContext({ req: { body: {
      item: {}
    } } })

    let tests = [
      [null, 'null'],
      ['', 'empty string'],
      ['asdf', 'string'],
      [0, 'empty number'],
      [123, 'number'],
      [[], 'array'],
    ]

    tests.forEach(function (check) {
      ctx.req.body.item = check[0]

      assert.throws(function() { verifyBody(ctx) }, function(err) {
        assert.ok(err instanceof HttpError)
        assert.ok(err instanceof Error)
        assert.strictEqual(err.status, 422)
        assert.match(err.message, /body/i)
        assert.match(err.message, /item/i)
        assert.match(err.message, /valid/i)
        return true
      }, `should fail if body entry is ${check[1]}`)
    })
  })

  let testInvalidNames = ['filename', 'path']

  testInvalidNames.forEach(function(invalidName) {
    t.test(`should fail if an item has the name ${invalidName}`, function() {
      let ctx = createContext({ req: { body: {
        [invalidName]: {}
      } } })
  
      assert.throws(function() { verifyBody(ctx) }, function(err) {
        assert.ok(err instanceof HttpError)
        assert.ok(err instanceof Error)
        assert.strictEqual(err.status, 422)
        assert.match(err.message, /body/i)
        assert.match(err.message, /name/i)
        assert.match(err.message, new RegExp(invalidName))
        assert.match(err.message, /allowed/i)
        return true
      }, 'should fail if body item has the name original')
    })
  })

  t.test('should require format string present in item', function() {
    let ctx = createContext({ req: { body: {
      item: {}
    } } })

    let tests = [
      [undefined, 'undefined'],
      [null, 'null'],
      ['', 'empty string'],
      [{}, 'object'],
      [0, 'empty number'],
      [123, 'number'],
      [[], 'array'],
      ['resize', 'not allow resize'],
      ['out', 'not allow out'],
    ]

    tests.forEach(function (check) {
      ctx.req.body.item = {
        format: check[0],
      }

      assert.throws(function() { verifyBody(ctx) }, function(err) {
        assert.ok(err instanceof HttpError)
        assert.ok(err instanceof Error)
        assert.strictEqual(err.status, 422)
        assert.match(err.message, /body/i)
        assert.match(err.message, /format/i)
        assert.match(err.message, /missing/i)
        return true
      }, `should fail if body item format is ${check[1]}`)
    })
  })

  t.test('should require object of same name as format', function() {
    let ctx = createContext({ req: { body: {
      item: {}
    } } })

    let tests = [
      [undefined, 'undefined'],
      [null, 'null'],
      ['', 'emptystring'],
      ['asdf', 'string'],
      [0, 'emptynumber'],
      [123, 'number'],
      [[], 'array'],
    ]

    tests.forEach(function (check) {
      ctx = createContext({ req: { body: {
        item: {}
      } } })

      ctx.req.body.item.format = check[1]
      ctx.req.body.item[check[1]] = check[0]

      assert.throws(function() { verifyBody(ctx) }, function(err) {
        assert.ok(err instanceof HttpError)
        assert.ok(err instanceof Error)
        assert.strictEqual(err.status, 422)
        assert.match(err.message, /body/i)
        assert.match(err.message, /format/i)
        assert.match(err.message, /options/i)
        assert.match(err.message, /valid/i)
        return true
      }, `should fail if body item format options is ${check[1]}`)
    })
  })

  t.test('should allow empty value or string in out in item', function() {
    let ctx = createContext({ req: { body: {
      item: {
        format: 'test',
        test: {},
      }
    } } })

    let tests = [
      [undefined, 'undefined'],
      [null, 'null'],
      ['', 'empty string'],
      ['file', 'allow string file'],
      ['base64', 'allow base64'],
    ]

    tests.forEach(function (check) {
      ctx.req.body.item.out = check[0]

      assert.doesNotThrow(function() {
        verifyBody(ctx)
      }, `should not throw with ${check[1]} in out`)
    })
  })
  
  t.test('should fail if out is invalid value', function() {
    let ctx = createContext({ req: { body: {
      item: {
        format: 'test',
        test: {},
      }
    } } })

    let tests = [
      [{}, 'object'],
      [0, 'empty number'],
      [123, 'number'],
      [[], 'array'],
      ['resize', 'not allow resize'],
      ['out', 'not allow out'],
      ['example', 'not allow example'],
    ]

    tests.forEach(function (check) {
      ctx.req.body.item.out = check[0]

      assert.throws(function() { verifyBody(ctx) }, function(err) {
        assert.ok(err instanceof HttpError)
        assert.ok(err instanceof Error)
        assert.strictEqual(err.status, 422)
        assert.match(err.message, /body/i)
        assert.match(err.message, /item/i)
        assert.match(err.message, /out/i)
        assert.match(err.message, /valid/i)
        return true
      }, `should fail if body item out is ${check[1]}`)
    })
  })

  let validObjectOperations = [
    'resize',
    'extend',
    'flatten',
  ]

  validObjectOperations.forEach(function(operation) {
    t.test(`should allow empty value or object in ${operation}`, function() {
      let ctx = createContext({ req: { body: {
        item: {
          format: 'test',
          test: {},
        }
      } } })
  
      let tests = [
        [undefined, 'undefined'],
        [null, 'null'],
        [{}, 'object'],
      ]
  
      tests.forEach(function (check) {
        ctx.req.body.item[operation] = check[0]
  
        assert.doesNotThrow(function() {
          verifyBody(ctx)
        }, `should not throw with ${check[1]} in ${operation}`)
      })
    })
  
    t.test(`should fail if ${operation} if specified is invalid`, function() {
      let ctx = createContext({ req: { body: {
        item: {
          format: 'test',
          test: {},
        }
      } } })
  
      let tests = [
        ['', 'emptystring'],
        ['asdf', 'string'],
        [0, 'emptynumber'],
        [123, 'number'],
        [[], 'array'],
      ]
  
      tests.forEach(function (check) {
        ctx.req.body.item[operation] = check[0]
  
        assert.throws(function() { verifyBody(ctx) }, function(err) {
          assert.ok(err instanceof HttpError)
          assert.ok(err instanceof Error)
          assert.strictEqual(err.status, 422)
          assert.match(err.message, /body/i)
          assert.match(err.message, /item/i)
          assert.match(err.message, new RegExp(operation))
          assert.match(err.message, /valid/i)
          return true
        }, `should fail if body item ${operation} is ${check[1]}`)
      })
    })
  })

  let validNumberOperations = [
    'blur',
    'trim',
  ]

  validNumberOperations.forEach(function(operation) {
    t.test(`should allow empty value or number in ${operation}`, function() {
      let ctx = createContext({ req: { body: {
        item: {
          format: 'test',
          test: {},
        }
      } } })
  
      let tests = [
        [undefined, 'undefined'],
        [null, 'null'],
        [0, 'number'],
        [0.5, 'positive number'],
      ]
  
      tests.forEach(function (check) {
        ctx.req.body.item[operation] = check[0]
  
        assert.doesNotThrow(function() {
          verifyBody(ctx)
        }, `should not throw with ${check[1]} in ${operation}`)
      })
    })
  
    t.test(`should fail if ${operation} if specified is invalid`, function() {
      let ctx = createContext({ req: { body: {
        item: {
          format: 'test',
          test: {},
        }
      } } })
  
      let tests = [
        ['', 'emptystring'],
        ['asdf', 'string'],
        [{}, 'object'],
        [[], 'array'],
      ]
  
      tests.forEach(function (check) {
        ctx.req.body.item[operation] = check[0]
  
        assert.throws(function() { verifyBody(ctx) }, function(err) {
          assert.ok(err instanceof HttpError)
          assert.ok(err instanceof Error)
          assert.strictEqual(err.status, 422)
          assert.match(err.message, /body/i)
          assert.match(err.message, /item/i)
          assert.match(err.message, new RegExp(operation))
          assert.match(err.message, /valid/i)
          return true
        }, `should fail if body item ${operation} is ${check[1]}`)
      })
    })
  })
})
