import time
from functools import partial
from unittest import mock

import asyncpraw
import praw
import pytest

from credmgr.exceptions import Conflict, InitializationError, NotFound, ServerError
from credmgr.models import RedditApp


def test_create_reddit_app(recorder, credential_manager):
    data = {
        "name": "test_reddit_app",
        "client_id": "client_id",
        "client_secret": "client_secret",
        "user_agent": "user_agent",
        "app_description": "app_description",
    }
    reddit_app = credential_manager.reddit_app.create(**data)
    for key, value in data.items():
        assert getattr(reddit_app, key) == value


def test_create_reddit_app_other_user(recorder, credential_manager):
    data = {"name": "test_reddit_app", "client_id": "client_id", "owner": 4}
    reddit_app = credential_manager.reddit_app.create(**data)
    for key, value in data.items():
        if key == "owner":
            assert getattr(reddit_app, key).id == value
            continue
        assert getattr(reddit_app, key) == value


def test_create_reddit_app_bad_params(recorder, credential_manager):
    data = {"name": "te", "client_id": "client_id_2"}
    with pytest.raises(ServerError):
        _ = credential_manager.reddit_app.create(**data)


def test_create_reddit_app_existing(recorder, credential_manager):
    data = {"name": "reddit_app", "client_id": "client_id"}
    with pytest.raises(Conflict):
        _ = credential_manager.reddit_app.create(**data)


def test_delete_reddit_app(recorder, credential_manager):
    reddit_app = credential_manager.reddit_app(3)
    reddit_app.delete()
    with pytest.raises(NotFound):
        _ = credential_manager.reddit_app(3)


def test_edit_reddit_app(recorder, credential_manager):
    reddit_app = credential_manager.reddit_app(6)
    reddit_app.edit(client_id="new_client_id")
    assert reddit_app.client_id == "new_client_id"


def test_edit_reddit_app_conflicting_data(recorder, credential_manager):
    reddit_app = credential_manager.reddit_app(6)
    with pytest.raises(Conflict):
        reddit_app.edit(client_id="client_id")


def test_list_reddit_apps(recorder, credential_manager):
    reddit_apps = credential_manager.reddit_apps()
    for reddit_app in reddit_apps:
        assert isinstance(reddit_app, RedditApp)


def test_list_reddit_apps_with_reddit_app(recorder, credential_manager):
    with pytest.raises(InitializationError):
        _ = credential_manager.reddit_app()


def test_create_reddit_app_without_user_agent(recorder, credential_manager):
    data = {
        "name": "test_reddit_app",
        "client_id": "client_id",
        "client_secret": "client_secret",
        "app_description": "app_description",
    }
    reddit_app = credential_manager.reddit_app.create(**data)
    assert reddit_app.user_agent == "python:test_reddit_app by u/Lil_SpazJoekp"
    for key, value in data.items():
        assert getattr(reddit_app, key) == value


class CustomSyncReddit(praw.Reddit):
    pass


class CustomAsyncReddit(asyncpraw.Reddit):
    pass


@pytest.mark.parametrize("use_async", [True, False], ids=["use_async", "not_use_async"])
@pytest.mark.parametrize("use_cache", [True, False], ids=["use_cache", "not_use_cache"])
@pytest.mark.parametrize(
    "use_custom_reddit_class",
    [True, False],
    ids=["custom_reddit_class", "no_custom_class"],
)
def test_reddit_app_reddit(
    recorder, credential_manager, use_async, use_cache, use_custom_reddit_class
):
    reddit_app = credential_manager.reddit_app(2)
    reddit_class = None
    if use_custom_reddit_class:
        reddit_class = CustomAsyncReddit if use_async else CustomSyncReddit
    reddit = reddit_app.reddit(use_async=use_async, reddit_class=reddit_class)
    same = reddit is reddit_app.reddit(
        use_async=use_async, use_cache=use_cache, reddit_class=reddit_class
    )
    assert (
        type(reddit) is (CustomAsyncReddit if use_async else CustomSyncReddit)
        if reddit_class
        else (asyncpraw.Reddit if use_async else praw.Reddit)
    )
    assert same == use_cache
    assert isinstance(reddit, praw.Reddit if not use_async else asyncpraw.Reddit)


def patched__request_token(self, **data):
    payload = {
        "access_token": "access_token",
        "token_type": "bearer",
        "expires_in": 3600,
        "refresh_token": "new_token",
        "scope": "account creddits edit flair history identity livemanage modconfig modcontributors modflair modlog modmail modothers modposts modself modtraffic modwiki mysubreddits privatemessages read report save structuredstyles submit subscribe vote wikiedit wikiread",
    }
    pre_request_time = time.time()
    self._expiration_timestamp = pre_request_time - 10 + payload["expires_in"]
    self.access_token = payload["access_token"]
    if "refresh_token" in payload:
        self.refresh_token = payload["refresh_token"]
    self.scopes = set(payload["scope"].split(" "))


def test_reddit_app_reddit_with_redditor(recorder, credential_manager):
    reddit_app = credential_manager.reddit_app(2)
    reddit = reddit_app.reddit("Lil_SpazJoekp")
    assert isinstance(reddit, praw.Reddit)
    assert not reddit.read_only
    response = mock.Mock(status_code=200)
    response.json.return_value = {"name": "Lil_SpazJoekp"}
    with mock.patch(
        "prawcore.requestor.Requestor.request", return_value=response, create=True
    ):
        with mock.patch("prawcore.rate_limit.RateLimiter.delay"):
            with mock.patch("prawcore.rate_limit.RateLimiter.update"):
                reddit._core._authorizer._request_token = partial(
                    patched__request_token, self=reddit._core._authorizer
                )
                reddit._core._authorizer.refresh()
                assert reddit.user.me() == "Lil_SpazJoekp"


def test_reddit_app_reddit_with_bad_redditor(recorder, credential_manager):
    reddit_app = credential_manager.reddit_app(2)
    with pytest.raises(NotFound):
        _ = reddit_app.reddit("BadRedditor")


def test_reddit_app_gen_auth_url_verification_scopes(recorder, credential_manager):
    reddit_app = credential_manager.reddit_app(19)
    url = reddit_app.gen_auth_url(scopes="read")
    assert (
        url
        == "https://www.reddit.com/api/v1/authorize?client_id=client_id&duration=temporary&redirect_uri=https%3A%2F%2Fcredmgr.jesassn.org%2Foauth2%2Freddit_callback&response_type=code&scope=read+identity&state=91893362b02d01c00fb71bc6be02065dedc3356595fc648581ab59a94864a7e4"
    )


def test_reddit_app_gen_auth_url_verification_all_scopes(recorder, credential_manager):
    reddit_app = credential_manager.reddit_app(19)
    url = reddit_app.gen_auth_url(scopes="all")
    assert (
        url
        == "https://www.reddit.com/api/v1/authorize?client_id=client_id&duration=temporary&redirect_uri=https%3A%2F%2Fcredmgr.jesassn.org%2Foauth2%2Freddit_callback&response_type=code&scope=%2A&state=91893362b02d01c00fb71bc6be02065dedc3356595fc648581ab59a94864a7e4"
    )


def test_reddit_app_gen_auth_url_verification(recorder, credential_manager):
    reddit_app = credential_manager.reddit_app(6)
    url = reddit_app.gen_auth_url()
    assert (
        url
        == "https://www.reddit.com/api/v1/authorize?client_id=new_client_id&duration=temporary&redirect_uri=https%3A%2F%2Fcredmgr.jesassn.org%2Foauth2%2Freddit_callback&response_type=code&scope=identity&state=7ce31a95e1abc2a41673406ff87da51932acf1047cbf2eec1beb1f783d81cfd4"
    )


def test_reddit_app_gen_auth_url_authencation(recorder, credential_manager):
    reddit_app = credential_manager.reddit_app(6)
    url = reddit_app.gen_auth_url(permanent=True)
    assert (
        url
        == "https://www.reddit.com/api/v1/authorize?client_id=new_client_id&duration=permanent&redirect_uri=https%3A%2F%2Fcredmgr.jesassn.org%2Foauth2%2Freddit_callback&response_type=code&scope=identity&state=7ce31a95e1abc2a41673406ff87da51932acf1047cbf2eec1beb1f783d81cfd4"
    )


def test_reddit_app_gen_auth_url_user_verification(recorder, credential_manager):
    reddit_app = credential_manager.reddit_app(6)
    url = reddit_app.gen_auth_url(user_verification=22)
    assert (
        url
        == "https://www.reddit.com/api/v1/authorize?client_id=new_client_id&duration=temporary&redirect_uri=https%3A%2F%2Fcredmgr.jesassn.org%2Foauth2%2Freddit_callback&response_type=code&scope=identity&state=N2NlMzFhOTVlMWFiYzJhNDE2NzM0MDZmZjg3ZGE1MTkzMmFjZjEwNDdjYmYyZWVjMWJlYjFmNzgzZDgxY2ZkNDoyMg%3D%3D"
    )


def test_reddit_app_gen_auth_url_user_verification_create(recorder, credential_manager):
    reddit_app = credential_manager.reddit_app(19)
    url = reddit_app.gen_auth_url(user_verification="new_id")
    assert (
        url
        == "https://www.reddit.com/api/v1/authorize?client_id=client_id&duration=temporary&redirect_uri=https%3A%2F%2Fcredmgr.jesassn.org%2Foauth2%2Freddit_callback&response_type=code&scope=identity&state=OTE4OTMzNjJiMDJkMDFjMDBmYjcxYmM2YmUwMjA2NWRlZGMzMzU2NTk1ZmM2NDg1ODFhYjU5YTk0ODY0YTdlNDpuZXdfaWQ%3D"
    )


def test_reddit_app_gen_auth_url_user_verification_create_existing(
    recorder, credential_manager
):
    reddit_app = credential_manager.reddit_app(22)
    url = reddit_app.gen_auth_url(user_verification="new_id")
    assert (
        url
        == "https://www.reddit.com/api/v1/authorize?client_id=client_id&duration=temporary&redirect_uri=https%3A%2F%2Fcredmgr.jesassn.org%2Foauth2%2Freddit_callback&response_type=code&scope=identity&state=OTE4OTMzNjJiMDJkMDFjMDBmYjcxYmM2YmUwMjA2NWRlZGMzMzU2NTk1ZmM2NDg1ODFhYjU5YTk0ODY0YTdlNDpuZXdfaWQ%3D"
    )
