import json
import time
import pytest
from requests.auth import _basic_auth_str
from flask import url_for

from superdesk.auth_server.scopes import Scope

from ..conftest import get_test_prodapi_app


RESOURCES = (
    'archive',
    'assignments',
    'contacts',
    'desks',
    'events',
    'planning',
    'users'
)

SCOPES = tuple(i.name for i in Scope)


def test_not_authenticated(prodapi_app, prodapi_client):
    """
    Send a not authenticated request:
        - make a request without a token
        - ensure that request is not authenticated by prod api

    :param prodapi_app: prod api app
    :type prodapi_app: eve.flaskapp.Eve
    :param prodapi_client: client for prod api
    :type prodapi_client: flask.testing.FlaskClient
    """

    with prodapi_app.test_request_context():
        for resource in RESOURCES:
            # we send a request without a token
            resp = prodapi_client.get(
                url_for('{}|resource'.format(resource))
            )
            resp_data = json.loads(resp.data.decode('utf-8'))
            # we get a 401 response
            assert resp.status_code == 401
            assert resp_data['_status'] == 'ERR'


@pytest.mark.parametrize('auth_server_registered_clients', [(('ARCHIVE_READ',),)], indirect=True)
def test_authenticated(superdesk_app, superdesk_client, auth_server_registered_clients):
    """
    Send an authenticated request:
        - register client for auth server
        - retrieve an acceess token from auth server by providing `id` and `password`
        - make a request with a token
        - ensure that request is authenticated by prod api

    :param superdesk_app: superdesk api app
    :type superdesk_app: eve.flaskapp.Eve
    :param superdesk_client: client for superdesk api
    :type superdesk_client: flask.testing.FlaskClient
    """

    client_id = auth_server_registered_clients[0]['client_id']
    password = auth_server_registered_clients[0]['password']

    # we send a client id and password to get an access token
    with superdesk_app.test_request_context():
        resp = superdesk_client.post(
            url_for('auth_server.issue_token'),
            data={
                'grant_type': 'client_credentials'
            },
            headers={
                'Authorization': _basic_auth_str(client_id, password)
            }
        )

    # we get an access token
    resp_data = json.loads(resp.data.decode('utf-8'))
    assert resp.status_code == 200
    assert resp_data['token_type'] == 'Bearer'
    assert 'access_token' in resp_data

    # we create a prodapi flask app and client
    prodapi_app = get_test_prodapi_app()
    prodapi_client = prodapi_app.test_client()

    # we send a request with an auth token
    token = resp_data['access_token']
    with prodapi_app.test_request_context():
        for resource in ('archive',):
            # we send a request with a token
            resp = prodapi_client.get(
                url_for('{}|resource'.format(resource)),
                headers={
                    'Authorization': 'Bearer {}'.format(token)
                }
            )

            # we get a 200 response
            assert resp.status_code == 200


@pytest.mark.parametrize('auth_server_registered_clients', [(('ARCHIVE_READ',),)], indirect=True)
def test_bad_shared_key(superdesk_app, superdesk_client, auth_server_registered_clients):
    """
    Send a request with a token which is signed and verified using different secrets:
        - register client for auth server
        - retrieve an acceess token from auth server by providing `id` and `password`
        - make a request with a token
        - ensure that request is NOT authenticated by prod api, because of different shared secret

    :param superdesk_app: superdesk api app
    :type superdesk_app: eve.flaskapp.Eve
    :param superdesk_client: client for superdesk api
    :type superdesk_client: flask.testing.FlaskClient
    """

    client_id = auth_server_registered_clients[0]['client_id']
    password = auth_server_registered_clients[0]['password']

    # we send a client id and password to get an access token
    with superdesk_app.test_request_context():
        resp = superdesk_client.post(
            url_for('auth_server.issue_token'),
            data={
                'grant_type': 'client_credentials'
            },
            headers={
                'Authorization': _basic_auth_str(client_id, password)
            }
        )

    # we get an access token
    resp_data = json.loads(resp.data.decode('utf-8'))
    token = resp_data['access_token']

    # we create a prodapi flask app and client
    prodapi_app = get_test_prodapi_app()
    prodapi_client = prodapi_app.test_client()

    # we change `AUTH_SERVER_SHARED_SECRET`
    prodapi_app.config['AUTH_SERVER_SHARED_SECRET'] = '7fZOf0VI9T70vU5uNlKLrc5GlabxVgl6'

    # we send a request with an auth token
    with prodapi_app.test_request_context():
        for resource in ('archive',):
            # we send a request with a token
            resp = prodapi_client.get(
                url_for('{}|resource'.format(resource)),
                headers={
                    'Authorization': 'Bearer {}'.format(token)
                }
            )
            resp_data = json.loads(resp.data.decode('utf-8'))

    # we get a 401 response
    assert resp.status_code == 401
    assert resp_data == {
        '_status': 'ERR',
        '_error': {
            'code': 401,
            'message': 'Please provide proper credentials'
        }
    }


@pytest.mark.parametrize('issued_tokens', [(SCOPES, tuple())], indirect=True)
def test_scopes(issued_tokens, prodapi_app, prodapi_client):
    """
    Send a request with 'all scopes' token:
        - make a requests to all endpoints with a token
        - ensure that all requests are authorized by prod api

    Send a request with 'empty scopes' token:
        - make a requests to all endpoints with a token
        - ensure that all requests are NOT authorized by prod api due to empty scopes

    :param issued_tokens: list of tokens provided by `issued_tokens` fixture
    :type issued_tokens: list
    :param prodapi_app: prod api app
    :type prodapi_app: eve.flaskapp.Eve
    :param prodapi_client: client for prod api
    :type prodapi_client: flask.testing.FlaskClient
    """

    access_token_all_scopes = issued_tokens[0]['access_token']
    access_token_no_scopes = issued_tokens[1]['access_token']

    with prodapi_app.test_request_context():
        for resource in RESOURCES:
            # we send a requests with 'empty scopes' token
            resp = prodapi_client.get(
                url_for('{}|resource'.format(resource)),
                headers={
                    'Authorization': 'Bearer {}'.format(access_token_no_scopes)
                }
            )
            resp_data = json.loads(resp.data.decode('utf-8'))
            # we get a 403 response
            assert resp.status_code == 403
            assert resp_data == {
                '_status': 'ERR',
                '_error': {
                    'code': 403,
                    'message': 'Invalid scope'
                }
            }
            # we send a requests with 'all scopes' token
            resp = prodapi_client.get(
                url_for('{}|resource'.format(resource)),
                headers={
                    'Authorization': 'Bearer {}'.format(access_token_all_scopes)
                }
            )
            # we get a 200 response
            assert resp.status_code == 200


@pytest.mark.parametrize('issued_tokens', [(("DESKS_READ",),)], indirect=True)
def test_scopes_partial(issued_tokens, prodapi_app):
    """
    Send a request with DESKS_READ scope token to `desks` and `items`:
        - make requests to `desks` and `items` endpoints
        - ensure that only request to `desk` is authorized

    :param prodapi_app: prod api app
    :type prodapi_app: eve.flaskapp.Eve
    :param issued_tokens: list of tokens provided by `issued_tokens` fixture
    :type issued_tokens: list
    """

    access_token = issued_tokens[0]['access_token']
    prodapi_client = prodapi_app.test_client()

    with prodapi_app.test_request_context():
        # we send a request with 'DESKS_READ scope' token to desks endpoint
        resp = prodapi_client.get(
            url_for('desks|resource'),
            headers={
                'Authorization': 'Bearer {}'.format(access_token)
            }
        )
        # we get a 200 response
        assert resp.status_code == 200

        # we send a request with 'DESKS_READ scope' token to items endpoint
        resp = prodapi_client.get(
            url_for('archive|resource'),
            headers={
                'Authorization': 'Bearer {}'.format(access_token)
            }
        )
        resp_data = json.loads(resp.data.decode('utf-8'))
        # we get a 403 response
        assert resp.status_code == 403
        assert resp_data == {
            '_status': 'ERR',
            '_error': {
                'code': 403,
                'message': 'Invalid scope'
            }
        }


@pytest.mark.parametrize('auth_server_registered_clients', [(('ARCHIVE_READ',),)], indirect=True)
@pytest.mark.parametrize('superdesk_app', [{'AUTH_SERVER_EXPIRATION_DELAY': 1}, ], indirect=True)
def test_token_expired(superdesk_app, superdesk_client, auth_server_registered_clients):
    """
    Send a request with an expired token:
        - register client for auth server
        - retrieve an acceess token from auth server by providing `id` and `password`
        - wait until token expires
        - make a request with an expired token
        - ensure that request is NOT authenticated by prod api

    :param superdesk_app: superdesk api app
    :type superdesk_app: eve.flaskapp.Eve
    :param superdesk_client: client for superdesk api
    :type superdesk_client: flask.testing.FlaskClient
    """

    client_id = auth_server_registered_clients[0]['client_id']
    password = auth_server_registered_clients[0]['password']
    expiration_delay = superdesk_app.config['AUTH_SERVER_EXPIRATION_DELAY']

    # we send a client id and password to get an access token
    with superdesk_app.test_request_context():
        resp = superdesk_client.post(
            url_for('auth_server.issue_token'),
            data={
                'grant_type': 'client_credentials'
            },
            headers={
                'Authorization': _basic_auth_str(client_id, password)
            }
        )

    # we get an access token
    assert resp.status_code == 200
    resp_data = json.loads(resp.data.decode('utf-8'))
    token = resp_data['access_token']

    # wait until token expire
    time.sleep(expiration_delay + 1)

    # we create a prodapi flask app and client
    prodapi_app = get_test_prodapi_app()
    prodapi_client = prodapi_app.test_client()

    # we send a request with an expired auth token
    with prodapi_app.test_request_context():
        for resource in ('archive',):
            # we send a request with a token
            resp = prodapi_client.get(
                url_for('{}|resource'.format(resource)),
                headers={
                    'Authorization': 'Bearer {}'.format(token)
                }
            )

    # we get a 401 response
    assert resp.status_code == 401
