from unittest import TestCase

import requests
import requests_mock
import time

from urllib.parse import urlparse, parse_qs

from oauthlib.oauth2.rfc6749.errors import InvalidGrantError
from requests_oauthlib import OAuth2Session
from requests_oauthlib.compliance_fixes import facebook_compliance_fix
from requests_oauthlib.compliance_fixes import fitbit_compliance_fix
from requests_oauthlib.compliance_fixes import mailchimp_compliance_fix
from requests_oauthlib.compliance_fixes import weibo_compliance_fix
from requests_oauthlib.compliance_fixes import slack_compliance_fix
from requests_oauthlib.compliance_fixes import instagram_compliance_fix
from requests_oauthlib.compliance_fixes import plentymarkets_compliance_fix
from requests_oauthlib.compliance_fixes import ebay_compliance_fix


class FacebookComplianceFixTest(TestCase):
    def setUp(self):
        mocker = requests_mock.Mocker()
        mocker.post(
            "https://graph.facebook.com/oauth/access_token",
            text="access_token=urlencoded",
            headers={"Content-Type": "text/plain"},
        )
        mocker.start()
        self.addCleanup(mocker.stop)

        facebook = OAuth2Session("someclientid", redirect_uri="https://i.b")
        self.session = facebook_compliance_fix(facebook)

    def test_fetch_access_token(self):
        token = self.session.fetch_token(
            "https://graph.facebook.com/oauth/access_token",
            client_secret="someclientsecret",
            authorization_response="https://i.b/?code=hello",
        )
        self.assertEqual(token, {"access_token": "urlencoded", "token_type": "Bearer"})


class FitbitComplianceFixTest(TestCase):
    def setUp(self):
        self.mocker = requests_mock.Mocker()
        self.mocker.post(
            "https://api.fitbit.com/oauth2/token",
            json={"errors": [{"errorType": "invalid_grant"}]},
        )
        self.mocker.start()
        self.addCleanup(self.mocker.stop)

        fitbit = OAuth2Session("someclientid", redirect_uri="https://i.b")
        self.session = fitbit_compliance_fix(fitbit)

    def test_fetch_access_token(self):
        self.assertRaises(
            InvalidGrantError,
            self.session.fetch_token,
            "https://api.fitbit.com/oauth2/token",
            client_secret="someclientsecret",
            authorization_response="https://i.b/?code=hello",
        )

        self.mocker.post(
            "https://api.fitbit.com/oauth2/token", json={"access_token": "fitbit"}
        )

        token = self.session.fetch_token(
            "https://api.fitbit.com/oauth2/token", client_secret="good"
        )

        self.assertEqual(token, {"access_token": "fitbit"})

    def test_refresh_token(self):
        self.assertRaises(
            InvalidGrantError,
            self.session.refresh_token,
            "https://api.fitbit.com/oauth2/token",
            auth=requests.auth.HTTPBasicAuth("someclientid", "someclientsecret"),
        )

        self.mocker.post(
            "https://api.fitbit.com/oauth2/token",
            json={"access_token": "access", "refresh_token": "refresh"},
        )

        token = self.session.refresh_token(
            "https://api.fitbit.com/oauth2/token",
            auth=requests.auth.HTTPBasicAuth("someclientid", "someclientsecret"),
        )

        self.assertEqual(token["access_token"], "access")
        self.assertEqual(token["refresh_token"], "refresh")


class MailChimpComplianceFixTest(TestCase):
    def setUp(self):
        mocker = requests_mock.Mocker()
        mocker.post(
            "https://login.mailchimp.com/oauth2/token",
            json={"access_token": "mailchimp", "expires_in": 0, "scope": None},
        )
        mocker.start()
        self.addCleanup(mocker.stop)

        mailchimp = OAuth2Session("someclientid", redirect_uri="https://i.b")
        self.session = mailchimp_compliance_fix(mailchimp)

    def test_fetch_access_token(self):
        token = self.session.fetch_token(
            "https://login.mailchimp.com/oauth2/token",
            client_secret="someclientsecret",
            authorization_response="https://i.b/?code=hello",
        )
        # Times should be close
        approx_expires_at = time.time() + 3600
        actual_expires_at = token.pop("expires_at")
        self.assertAlmostEqual(actual_expires_at, approx_expires_at, places=2)

        # Other token values exact
        self.assertEqual(token, {"access_token": "mailchimp", "expires_in": 3600})

        # And no scope at all
        self.assertNotIn("scope", token)


class WeiboComplianceFixTest(TestCase):
    def setUp(self):
        mocker = requests_mock.Mocker()
        mocker.post(
            "https://api.weibo.com/oauth2/access_token", json={"access_token": "weibo"}
        )
        mocker.start()
        self.addCleanup(mocker.stop)

        weibo = OAuth2Session("someclientid", redirect_uri="https://i.b")
        self.session = weibo_compliance_fix(weibo)

    def test_fetch_access_token(self):
        token = self.session.fetch_token(
            "https://api.weibo.com/oauth2/access_token",
            client_secret="someclientsecret",
            authorization_response="https://i.b/?code=hello",
        )
        self.assertEqual(token, {"access_token": "weibo", "token_type": "Bearer"})


class SlackComplianceFixTest(TestCase):
    def setUp(self):
        mocker = requests_mock.Mocker()
        mocker.post(
            "https://slack.com/api/oauth.access",
            json={"access_token": "xoxt-23984754863-2348975623103", "scope": "read"},
        )
        for method in ("GET", "POST"):
            mocker.request(
                method=method,
                url="https://slack.com/api/auth.test",
                json={
                    "ok": True,
                    "url": "https://myteam.slack.com/",
                    "team": "My Team",
                    "user": "cal",
                    "team_id": "T12345",
                    "user_id": "U12345",
                },
            )
        mocker.start()
        self.addCleanup(mocker.stop)

        slack = OAuth2Session("someclientid", redirect_uri="https://i.b")
        self.session = slack_compliance_fix(slack)

    def test_protected_request(self):
        self.session.token = {"access_token": "dummy-access-token"}
        response = self.session.get("https://slack.com/api/auth.test")
        url = response.request.url
        query = parse_qs(urlparse(url).query)
        self.assertNotIn("token", query)
        body = response.request.body
        data = parse_qs(body)
        self.assertEqual(data["token"], ["dummy-access-token"])

    def test_protected_request_override_token_get(self):
        self.session.token = {"access_token": "dummy-access-token"}
        response = self.session.get(
            "https://slack.com/api/auth.test", data={"token": "different-token"}
        )
        url = response.request.url
        query = parse_qs(urlparse(url).query)
        self.assertNotIn("token", query)
        body = response.request.body
        data = parse_qs(body)
        self.assertEqual(data["token"], ["different-token"])

    def test_protected_request_override_token_post(self):
        self.session.token = {"access_token": "dummy-access-token"}
        response = self.session.post(
            "https://slack.com/api/auth.test", data={"token": "different-token"}
        )
        url = response.request.url
        query = parse_qs(urlparse(url).query)
        self.assertNotIn("token", query)
        body = response.request.body
        data = parse_qs(body)
        self.assertEqual(data["token"], ["different-token"])

    def test_protected_request_override_token_url(self):
        self.session.token = {"access_token": "dummy-access-token"}
        response = self.session.get(
            "https://slack.com/api/auth.test?token=different-token"
        )
        url = response.request.url
        query = parse_qs(urlparse(url).query)
        self.assertEqual(query["token"], ["different-token"])
        self.assertIsNone(response.request.body)


class InstagramComplianceFixTest(TestCase):
    def setUp(self):
        mocker = requests_mock.Mocker()
        mocker.request(
            method="GET",
            url="https://api.instagram.com/v1/users/self",
            json={
                "data": {
                    "id": "1574083",
                    "username": "snoopdogg",
                    "full_name": "Snoop Dogg",
                    "profile_picture": "http://distillery.s3.amazonaws.com/profiles/profile_1574083_75sq_1295469061.jpg",
                    "bio": "This is my bio",
                    "website": "http://snoopdogg.com",
                    "is_business": False,
                    "counts": {"media": 1320, "follows": 420, "followed_by": 3410},
                }
            },
        )
        mocker.start()
        self.addCleanup(mocker.stop)

        instagram = OAuth2Session("someclientid", redirect_uri="https://i.b")
        self.session = instagram_compliance_fix(instagram)

    def test_protected_request(self):
        self.session.token = {"access_token": "dummy-access-token"}
        response = self.session.get("https://api.instagram.com/v1/users/self")
        url = response.request.url
        query = parse_qs(urlparse(url).query)
        self.assertIn("access_token", query)
        self.assertEqual(query["access_token"], ["dummy-access-token"])

    def test_protected_request_dont_override(self):
        """check that if the access_token param
        already exist we don't override it"""
        self.session.token = {"access_token": "dummy-access-token"}
        response = self.session.get(
            "https://api.instagram.com/v1/users/self?access_token=correct-access-token"
        )
        url = response.request.url
        query = parse_qs(urlparse(url).query)
        self.assertIn("access_token", query)
        self.assertEqual(query["access_token"], ["correct-access-token"])


class PlentymarketsComplianceFixTest(TestCase):
    def setUp(self):
        mocker = requests_mock.Mocker()
        mocker.post(
            "https://shop.plentymarkets-cloud02.com",
            json={
                "accessToken": "ecUN1r8KhJewMCdLAmpHOdZ4O0ofXKB9zf6CXK61",
                "tokenType": "Bearer",
                "expiresIn": 86400,
                "refreshToken": "iG2kBGIjcXaRE4xmTVUnv7xwxX7XMcWCHqJmFaSX",
            },
            headers={"Content-Type": "application/json"},
        )
        mocker.start()
        self.addCleanup(mocker.stop)

        plentymarkets = OAuth2Session("someclientid", redirect_uri="https://i.b")
        self.session = plentymarkets_compliance_fix(plentymarkets)

    def test_fetch_access_token(self):
        token = self.session.fetch_token(
            "https://shop.plentymarkets-cloud02.com",
            authorization_response="https://i.b/?code=hello",
        )

        approx_expires_at = time.time() + 86400
        actual_expires_at = token.pop("expires_at")
        self.assertAlmostEqual(actual_expires_at, approx_expires_at, places=2)

        self.assertEqual(
            token,
            {
                "access_token": "ecUN1r8KhJewMCdLAmpHOdZ4O0ofXKB9zf6CXK61",
                "expires_in": 86400,
                "token_type": "Bearer",
                "refresh_token": "iG2kBGIjcXaRE4xmTVUnv7xwxX7XMcWCHqJmFaSX",
            },
        )


class EbayComplianceFixTest(TestCase):
    def setUp(self):
        mocker = requests_mock.Mocker()
        mocker.post(
            "https://api.ebay.com/identity/v1/oauth2/token",
            json={
                "access_token": "this is the access token",
                "expires_in": 7200,
                "token_type": "Application Access Token",
            },
            headers={"Content-Type": "application/json"},
        )
        mocker.start()
        self.addCleanup(mocker.stop)

        session = OAuth2Session()
        self.fixed_session = ebay_compliance_fix(session)

    def test_fetch_access_token(self):
        token = self.fixed_session.fetch_token(
            "https://api.ebay.com/identity/v1/oauth2/token",
            authorization_response="https://i.b/?code=hello",
        )
        assert token["token_type"] == "Bearer"


def access_and_refresh_token_request_compliance_fix_test(session, client_secret):
    def _non_compliant_header(url, headers, body):
        headers["X-Client-Secret"] = client_secret
        return url, headers, body

    session.register_compliance_hook("access_token_request", _non_compliant_header)
    session.register_compliance_hook("refresh_token_request", _non_compliant_header)
    return session


class RefreshTokenRequestComplianceFixTest(TestCase):
    value_to_test_for = "value_to_test_for"

    def setUp(self):
        mocker = requests_mock.Mocker()
        mocker.post(
            "https://example.com/token",
            request_headers={"X-Client-Secret": self.value_to_test_for},
            json={
                "access_token": "this is the access token",
                "expires_in": 7200,
                "token_type": "Bearer",
            },
            headers={"Content-Type": "application/json"},
        )
        mocker.post(
            "https://example.com/refresh",
            request_headers={"X-Client-Secret": self.value_to_test_for},
            json={
                "access_token": "this is the access token",
                "expires_in": 7200,
                "token_type": "Bearer",
            },
            headers={"Content-Type": "application/json"},
        )
        mocker.start()
        self.addCleanup(mocker.stop)

        session = OAuth2Session()
        self.fixed_session = access_and_refresh_token_request_compliance_fix_test(
            session, self.value_to_test_for
        )

    def test_access_token(self):
        token = self.fixed_session.fetch_token(
            "https://example.com/token",
            authorization_response="https://i.b/?code=hello",
        )
        assert token["token_type"] == "Bearer"

    def test_refresh_token(self):
        token = self.fixed_session.refresh_token(
            "https://example.com/refresh",
        )
        assert token["token_type"] == "Bearer"