import urllib

from django.urls import include, path
from django.test import override_settings
from django.conf import settings
import requests

import ichec_django_core.urls
from ichec_django_core.views.auth import NoRedirectOIDCLogoutView
from ichec_django_core.auth.backends import MemberOIDCAuthenticationBackend
from ichec_django_core.middleware.oidc import RefreshOIDCAccessToken

from ichec_django_core.test import (
    setup_default_users_and_groups,
    add_group_permissions,
    AuthAPITestCase,
)

urlpatterns = [
    path("oidc/logout/", NoRedirectOIDCLogoutView.as_view(), name="oidc_logout"),
    path("oidc/", include("mozilla_django_oidc.urls")),
]


class TestOIDCBackend(MemberOIDCAuthenticationBackend):

    def __init__(self):
        super().__init__()
        self.last_request = None
        self.default_token_response = {
            "id_token": "test_id_token",
            "access_token": "test_access_token",
            "refresh_token": "test_refresh_token",
        }
        self.next_token_reponse = None

    def get_token(self, request):
        self.last_request = request
        if self.next_token_reponse:
            return self.next_token_reponse
        return self.default_token_response

    def verify_token(self, id_token, nonce):
        return {"payload": "content"}

    def get_userinfo(self, access_token, _id_token, _payload):
        return {"email": "oidc_user@email.com", "preferred_username": "oidc_user"}


def get_response(request):
    return None


class MockResponse:

    def __init__(self):
        self.status_code = 400
        self.text = "Bad Request"


class TestAccessTokenRefresh(RefreshOIDCAccessToken):

    def __init__(self, get_response):
        super().__init__(get_response)
        self.timed_out = False

    def is_expired(self, request):
        return True

    def _do_refresh(self, token_url, token_payload):
        if self.timed_out:
            raise requests.exceptions.HTTPError(response=MockResponse())

        return {
            "access_token": "refreshed_access_token",
            "refresh_token": "refreshed_refresh_token",
        }


@override_settings(WITH_OIDC=True)
@override_settings(OIDC_OP_JWKS_ENDPOINT="/jjkws")
@override_settings(OIDC_RP_CLIENT_ID="test_client")
@override_settings(OIDC_RP_CLIENT_SECRET="client_secret")
@override_settings(OIDC_OP_TOKEN_ENDPOINT="/token_endpoint")
@override_settings(OIDC_AUTHENTICATION_CALLBACK_URL="register")
class TestOIDCLoginView(AuthAPITestCase):

    urls = "ichec_django_core.urls"

    def setUp(self):

        ichec_django_core.urls.urlpatterns += urlpatterns
        setup_default_users_and_groups()
        self.auth_backend = TestOIDCBackend()
        self.refresh_middleware = TestAccessTokenRefresh(get_response)

    def test_login_flow(self):
        response = self.client.get("/oidc/authenticate/?next=abc")

        # Verify response
        self.assertEqual(response.status_code, 302)
        queries = response.url.split("?")[1].split("&")

        query_dict = {}
        for q in queries:
            k, v = q.split("=")
            query_dict[k] = v

        self.assertTrue("redirect_uri" in query_dict)
        query_dict["redirect_uri"] = urllib.parse.unquote(
            query_dict["redirect_uri"]
        ).split("testserver")[1]

        # Verify session state
        session = self.client.session
        self.assertTrue(len(session["oidc_states"]) > 0)
        self.assertTrue(query_dict["state"] in session["oidc_states"])

        # Simulate calling the callback url
        # This won't succeed at logging in the user,
        # as it is difficult to inject the
        # auth backend we need, but it will execute most of the
        # relevant code
        callback_response = self.client.get(
            "/oidc/callback/",
            query_params={"code": "abc123", "state": query_dict["state"]},
        )
        self.assertEqual(response.status_code, 302)

        # Now we manually authenticate
        kwargs = {
            "request": callback_response.wsgi_request,
            "nonce": query_dict["nonce"],
            "code_verifier": session["oidc_states"][query_dict["state"]][
                "code_verifier"
            ],
        }
        user = self.auth_backend.authenticate(**kwargs)

        self.assertTrue(user is not None)
        self.assertEqual(self.auth_backend.last_request["client_id"], "test_client")
        self.assertEqual(
            self.auth_backend.last_request["client_secret"], "client_secret"
        )
        self.assertEqual(user.username, "oidc_user")

        # Verify that we've stored a refresh token
        self.assertEqual(
            callback_response.wsgi_request.session["oidc_refresh_token"],
            "test_refresh_token",
        )

        # Pass the request through the refresh middleware
        refresh_response = self.refresh_middleware.process_request(
            callback_response.wsgi_request
        )
        self.assertEqual(refresh_response, None)

        # Verify that we've stored a new refresh token and reset the expiration timer
        self.assertEqual(
            callback_response.wsgi_request.session["oidc_refresh_token"],
            "refreshed_refresh_token",
        )
        self.assertTrue(
            callback_response.wsgi_request.session["oidc_id_token_expiration"] > 0
        )

        # Now simulate that our oidc session has timed out
        self.refresh_middleware.timed_out = True
        refresh_response = self.refresh_middleware.process_request(
            callback_response.wsgi_request
        )
        self.assertEqual(refresh_response.status_code, 302)
