import time as real_time
import unittest

import jwt as jwt_lib
from mock import patch

from twilio.jwt import Jwt, JwtDecodeError


class DummyJwt(Jwt):
    """Jwt implementation that allows setting arbitrary payload and headers for testing."""

    ALGORITHM = 'HS256'

    def __init__(self, secret_key, issuer, subject=None, algorithm=None,
                 nbf=Jwt.GENERATE, ttl=3600, valid_until=None, headers=None,
                 payload=None):
        super(DummyJwt, self).__init__(
            secret_key=secret_key,
            issuer=issuer,
            subject=subject,
            algorithm=algorithm or self.ALGORITHM,
            nbf=nbf,
            ttl=ttl,
            valid_until=valid_until
        )
        self._payload = payload or {}
        self._headers = headers or {}

    def _generate_payload(self):
        return self._payload

    def _generate_headers(self):
        return self._headers


class JwtTest(unittest.TestCase):
    def assertIn(self, foo, bar, msg=None):
        """backport for 2.6"""
        assert foo in bar, (msg or "%s not found in %s" % (foo, bar))

    def now(self):
        return int(real_time.time())

    def assertJwtsEqual(self, jwt, key, expected_payload=None, expected_headers=None):
        expected_headers = expected_headers or {}
        expected_payload = expected_payload or {}

        decoded_payload = jwt_lib.decode(jwt, key, algorithms=["HS256"], options={"verify_signature": False})
        decoded_headers = jwt_lib.get_unverified_header(jwt)

        self.assertEqual(expected_headers, decoded_headers)
        self.assertEqual(expected_payload, decoded_payload)

    @patch('time.time')
    def test_basic_encode(self, time_mock):
        time_mock.return_value = 0.0

        jwt = DummyJwt('secret_key', 'issuer', headers={}, payload={})

        self.assertJwtsEqual(
            jwt.to_jwt(), 'secret_key',
            expected_headers={'typ': 'JWT', 'alg': 'HS256'},
            expected_payload={'iss': 'issuer', 'exp': 3600, 'nbf': 0},
        )

    @patch('time.time')
    def test_encode_with_subject(self, time_mock):
        time_mock.return_value = 0.0

        jwt = DummyJwt('secret_key', 'issuer', subject='subject', headers={}, payload={})

        self.assertJwtsEqual(
            jwt.to_jwt(), 'secret_key',
            expected_headers={'typ': 'JWT', 'alg': 'HS256'},
            expected_payload={'iss': 'issuer', 'exp': 3600, 'nbf': 0, 'sub': 'subject'},
        )

    @patch('time.time')
    def test_encode_without_nbf(self, time_mock):
        time_mock.return_value = 0.0

        jwt = DummyJwt('secret_key', 'issuer', subject='subject', headers={}, payload={}, nbf=None)

        self.assertJwtsEqual(
            jwt.to_jwt(), 'secret_key',
            expected_headers={'typ': 'JWT', 'alg': 'HS256'},
            expected_payload={'iss': 'issuer', 'exp': 3600, 'sub': 'subject'},
        )

    @patch('time.time')
    def test_encode_custom_ttl(self, time_mock):
        time_mock.return_value = 0.0

        jwt = DummyJwt('secret_key', 'issuer', ttl=10, headers={}, payload={})

        self.assertJwtsEqual(
            jwt.to_jwt(), 'secret_key',
            expected_headers={'typ': 'JWT', 'alg': 'HS256'},
            expected_payload={'iss': 'issuer', 'exp': 10, 'nbf': 0},