import time

import pytest
from flask import json

from authlib.common.urls import add_params_to_uri
from authlib.oauth1.rfc5849 import signature
from tests.util import read_file_path

from .oauth1_server import Client
from .oauth1_server import TokenCredential
from .oauth1_server import User
from .oauth1_server import create_resource_server


@pytest.fixture(autouse=True)
def user(db):
    user = User(username="foo")
    db.session.add(user)
    db.session.commit()
    yield user
    db.session.delete(user)


@pytest.fixture(autouse=True)
def client(db, user):
    client = Client(
        user_id=user.id,
        client_id="client",
        client_secret="secret",
        default_redirect_uri="https://client.test",
    )
    db.session.add(client)
    db.session.commit()
    yield client
    db.session.delete(client)


@pytest.fixture(autouse=True)
def token(db, user, client):
    tok = TokenCredential(
        user_id=user.id,
        client_id=client.client_id,
        oauth_token="valid-token",
        oauth_token_secret="valid-token-secret",
    )
    db.session.add(tok)
    db.session.commit()


@pytest.mark.parametrize("use_cache", [True, False])
def test_invalid_request_parameters(app, test_client, use_cache):
    create_resource_server(app, use_cache, use_cache)
    url = "/user"

    # case 1
    rv = test_client.get(url)
    data = json.loads(rv.data)
    assert data["error"] == "missing_required_parameter"
    assert "oauth_consumer_key" in data["error_description"]

    # case 2
    rv = test_client.get(add_params_to_uri(url, {"oauth_consumer_key": "a"}))
    data = json.loads(rv.data)
    assert data["error"] == "invalid_client"

    # case 3
    rv = test_client.get(add_params_to_uri(url, {"oauth_consumer_key": "client"}))
    data = json.loads(rv.data)
    assert data["error"] == "missing_required_parameter"
    assert "oauth_token" in data["error_description"]

    # case 4
    rv = test_client.get(
        add_params_to_uri(url, {"oauth_consumer_key": "client", "oauth_token": "a"})
    )
    data = json.loads(rv.data)
    assert data["error"] == "invalid_token"

    # case 5
    rv = test_client.get(
        add_params_to_uri(
            url, {"oauth_consumer_key": "client", "oauth_token": "valid-token"}
        )
    )
    data = json.loads(rv.data)
    assert data["error"] == "missing_required_parameter"
    assert "oauth_timestamp" in data["error_description"]


@pytest.mark.parametrize("use_cache", [True, False])
def test_plaintext_signature(app, test_client, use_cache):
    create_resource_server(app, use_cache, use_cache)
    url = "/user"

    # case 1: success
    auth_header = (
        'OAuth oauth_consumer_key="client",'
        'oauth_signature_method="PLAINTEXT",'
        'oauth_token="valid-token",'
        'oauth_signature="secret&valid-token-secret"'
    )
    headers = {"Authorization": auth_header}
    rv = test_client.get(url, headers=headers)
    data = json.loads(rv.data)
    assert "username" in data

    # case 2: invalid signature
    auth_header = auth_header.replace("valid-token-secret", "invalid")
    headers = {"Authorization": auth_header}
    rv = test_client.get(url, headers=headers)
    data = json.loads(rv.data)
    assert data["error"] == "invalid_signature"


@pytest.mark.parametrize("use_cache", [True, False])
def test_hmac_sha1_signature(app, test_client, use_cache):
    create_resource_server(app, use_cache, use_cache)
    url = "/user"

    params = [
        ("oauth_consumer_key", "client"),
        ("oauth_token", "valid-token"),
        ("oauth_signature_method", "HMAC-SHA1"),
        ("oauth_timestamp", str(int(time.time()))),
        ("oauth_nonce", "hmac-sha1-nonce"),
    ]
    base_string = signature.construct_base_string(
        "GET", "http://localhost/user", params
    )
    sig = signature.hmac_sha1_signature(base_string, "secret", "valid-token-secret")
    params.append(("oauth_signature", sig))
    auth_param = ",".join([f'{k}="{v}"' for k, v in params])
    auth_header = "OAuth " + auth_param
    headers = {"Authorization": auth_header}

    # case 1: success
    rv = test_client.get(url, headers=headers)
    data = json.loads(rv.data)
    assert "username" in data

    # case 2: exists nonce
    rv = test_client.get(url, headers=headers)
    data = json.loads(rv.data)
    assert data["error"] == "invalid_nonce"


@pytest.mark.parametrize("use_cache", [True, False])
def test_rsa_sha1_signature(app, test_client, use_cache):
    create_resource_server(app, use_cache, use_cache)
    url = "/user"

    params = [
        ("oauth_consumer_key", "client"),
        ("oauth_token", "valid-token"),
        ("oauth_signature_method", "RSA-SHA1"),
        ("oauth_timestamp", str(int(time.time()))),
        ("oauth_nonce", "rsa-sha1-nonce"),
    ]
    base_string = signature.construct_base_string(
        "GET", "http://localhost/user", params
    )
    sig = signature.rsa_sha1_signature(base_string, read_file_path("rsa_private.pem"))
    params.append(("oauth_signature", sig))
    auth_param = ",".join([f'{k}="{v}"' for k, v in params])
    auth_header = "OAuth " + auth_param
    headers = {"Authorization": auth_header}
    rv = test_client.get(url, headers=headers)
    data = json.loads(rv.data)
    assert "username" in data

    # case: invalid signature
    auth_param = auth_param.replace("rsa-sha1-nonce", "alt-sha1-nonce")
    auth_header = "OAuth " + auth_param
    headers = {"Authorization": auth_header}
    rv = test_client.get(url, headers=headers)
    data = json.loads(rv.data)
    assert data["error"] == "invalid_signature"


def test_decorator_without_parentheses(app, test_client):
    create_resource_server(app)
    auth_header = (
        'OAuth oauth_consumer_key="client",'
        'oauth_signature_method="PLAINTEXT",'
        'oauth_token="valid-token",'
        'oauth_signature="secret&valid-token-secret"'
    )
    headers = {"Authorization": auth_header}
    rv = test_client.get("/user-no-parens", headers=headers)
    data = json.loads(rv.data)
    assert "username" in data
