"""This file and its contents are licensed under the Apache License 2.0. Please see the included NOTICE for copyright information and LICENSE for a copy of the license.
"""
import pytest
import json
import requests_mock

from rest_framework.authtoken.models import Token
from rest_framework.test import APIClient
from .utils import ml_backend_mock

from projects.models import Project
from ml.models import MLBackendTrainJob
from tasks.models import Annotation, Task


@pytest.fixture
def client_and_token(business_client):
    token = Token.objects.get(user=business_client.business.admin)
    client = APIClient()
    client.credentials(HTTP_AUTHORIZATION='Token ' + token.key)
    client.organization_pk = business_client.organization.pk
    return client, token


@pytest.fixture(params=['business_authorized', 'user_with_token'])
def any_api_client(request, client_and_token, business_client):
    client, token = client_and_token
    result = {
        'type': request.param,
        'token': token
    }
    if request.param == 'business_authorized':
        result['client'] = business_client
    elif request.param == 'user_with_token':
        result['client'] = client
    return result


@pytest.mark.parametrize('payload, response, status_code', [
    # status OK
    (
        {"title": "111", "label_config": "<View><Text name=\"my_text\" value=\"$text\"/><Choices name=\"my_class\" toName=\"my_text\"><Choice value=\"pos\"/><Choice value=\"neg\"/></Choices></View>"},  # noqa
        None,
        201
    ),
    # invalid label config: unexisted toName
    (
        {"title": "111", "label_config": "<View><Text name=\"my_text\" value=\"$text\"/><Choices name=\"my_class\" toName=\"unexisted\"><Choice value=\"pos\"/><Choice value=\"neg\"/></Choices></View>"},  # noqa
        {"label_config": ["toName=\"unexisted\" not found in names: ['my_class', 'my_text']"]},
        400
    ),
    # invalid label config: missed toName
    (
        {"title": "111", "label_config": "<View><Text name=\"my_text\" value=\"$text\"/><Choices name=\"my_class\"><Choice value=\"pos\"/><Choice value=\"neg\"/></Choices></View>"},  # noqa
        {'label_config': ["Validation failed on : 'toName' is a required property"]},  # noqa
        400
    ),
    # empty label config
    (
        {"title": "111", "label_config": None},
        {'label_config': ['can only parse strings']},
        400
    )
])
@pytest.mark.django_db
def test_create_project(client_and_token, payload, response, status_code):
    client, token = client_and_token
    payload['organization_pk'] = client.organization_pk
    with ml_backend_mock():
        r = client.post(
            '/api/projects/',
            data=json.dumps(payload),
            content_type='application/json',
            headers={'Authorization': f'Token {token}'}
        )
    assert r.status_code == status_code
    if response:
        response_data = r.json()
        if r.status_code == 400:
            assert response_data['validation_errors'] == response
        else:
            assert response_data == response


@pytest.mark.parametrize('payload, response, status_code', [
    # status OK
    (
        {"label_config": "<View><Text name=\"my_text\" value=\"$text\"/><Choices name=\"my_class\" toName=\"my_text\"><Choice value=\"pos\"/><Choice value=\"neg\"/></Choices></View>"},  # noqa
        None,
        200
    ),
    # TODO: this should instead of next one, but "configured_project" fixture doesn't update project.summary with data columns
    # invalid column
    # (
    #     {"label_config": "<View><Text name=\"my_text\" value=\"$text\"/><Choices name=\"my_class\" toName=\"my_text\"><Choice value=\"pos\"/><Choice value=\"neg\"/></Choices></View>"},
    #     # noqa
    #     {'label_config': ['These fields are not found in data: text']},
    #     400
    # ),
    # invalid label config: unexisted toName
    (
        {"label_config": "<View><Text name=\"my_text\" value=\"$text\"/><Choices name=\"my_class\" toName=\"unexisted\"><Choice value=\"pos\"/><Choice value=\"neg\"/></Choices></View>"},  # noqa
        {"label_config": ["toName=\"unexisted\" not found in names: ['my_class', 'my_text']"]},
        400
    ),
    # invalid label config: missed toName
    (
        {"label_config": "<View><Text name=\"my_text\" value=\"$text\"/><Choices name=\"my_class\"><Choice value=\"pos\"/><Choice value=\"neg\"/></Choices></View>"}, # noqa
        {'label_config': ["Validation failed on : 'toName' is a required property"]},  # noqa
        400
    )
])
@pytest.mark.django_db
def test_patch_project(client_and_token, configured_project, payload, response, status_code):
    client, token = client_and_token
    payload['organization_pk'] = client.organization_pk
    r = client.patch(
        f'/api/projects/{configured_project.id}/',
        data=json.dumps(payload),
        content_type='application/json',
        headers={'Authorization': f'Token {token}'}
    )
    assert r.status_code == status_code
    if response:
        response_data = r.json()
        if r.status_code == 400:
            assert response_data['validation_errors'] == response
        else:
            assert response_data == response


@pytest.mark.parametrize('external_status_code, current_active_ml_backend_url, ml_backend_call_count', [
    (201, 'http://my.super.ai', 4),
])
@pytest.mark.django_db
def test_creating_activating_new_ml_backend(
    client_and_token, configured_project, external_status_code, current_active_ml_backend_url,
    ml_backend_call_count
):
    business_client, token = client_and_token
    with requests_mock.Mocker() as m:
        my_url = 'http://my.super.ai'
        m.post(f'{my_url}/setup', text=json.dumps({'model_version': 'Version from My Super AI'}))
        m.get(f'{my_url}/health', text=json.dumps({'status': 'UP'}))
        r = business_client.post(
            '/api/ml',
            data=json.dumps({
                'project': configured_project.id,
                'title': 'My Super AI',
                'url': my_url
            }),
            content_type='application/json',
            headers={'Authorization': f'Token {token}'}
        )
        assert r.status_code == external_status_code
        assert m.called
        assert m.call_count == ml_backend_call_count
        project = Project.objects.get(id=configured_project.id)
        all_urls = [m.url for m in project.ml_backends.all()]
        connected_ml = [url for url in all_urls if url == current_active_ml_backend_url]
        assert len(connected_ml) == 1, '\n'.join(all_urls)


@pytest.mark.django_db
def test_delete_annotations(business_client, configured_project):
    business_client.delete(f'/api/projects/{configured_project.id}/annotations/')
    assert not Annotation.objects.filter(task__project=configured_project.id).exists()


# --- TaskAPI ---

@pytest.mark.parametrize('response, status_code', [
    # status OK
    (
        {"annotations": [], 'predictions': [], 'drafts': [],
         "data": {"text": "text B", "meta_info": "meta info B"}, "meta": {},
         "created_at": "", "updated_at": "", 'updated_by': [], "is_labeled": False, "project": 0,
         'overlap': 1, 'file_upload': None, 'annotations_ids': '', 'annotations_results': '',
         'annotators': [], 'completed_at': None, 'predictions_model_versions': '',
         'predictions_results': '', 'predictions_score': None, 'total_annotations': 0, 'total_predictions': 0,
         'avg_lead_time': None, 'cancelled_annotations': 0, 'inner_id': 0,'storage_filename': None,
         'comment_authors': [], 'comment_count': 0, 'last_comment_updated_at': None, 'unresolved_comment_count': 0},
        200
    )
])
@pytest.mark.django_db
def test_get_task(client_and_token, configured_project, response, status_code):
    client, token = client_and_token
    task = configured_project.tasks.order_by('-id').all()[0]
    response['project'] = configured_project.id
    response['created_at'] = task.created_at.isoformat().replace('+00:00', 'Z')
    response['updated_at'] = task.updated_at.isoformat().replace('+00:00', 'Z')
    response['id'] = task.id
    r = client.get(
        f'/api/tasks/{task.id}/',
        content_type='application/json',
        headers={'Authorization': f'Token {token}'}
    )
    assert r.status_code == status_code
    if response:
        assert r.json() == response


@pytest.mark.parametrize('payload, response, status_code', [
    # status OK
    (
        {"annotations": [], 'predictions': [],
         "data": {"text": "TEST1", "meta_info": "TEST2"}, "meta": {},
         "created_at": "", "updated_at": "", "updated_by": None, "is_labeled": False,
         "project": 0, 'file_upload': None},
        {"id": 0, "annotations": [], 'predictions': [],
         "data": {"text": "TEST1", "meta_info": "TEST2"}, "meta": {},
         "created_at": "", "updated_at": "", "updated_by": None, "is_labeled": False, "project": 0,
         'overlap': 1, 'file_upload': None, "inner_id": 1,
         'comment_authors': [], 'comment_count': 0, 'last_comment_updated_at': None, 'unresolved_comment_count': 0},
        200
    )
])
@pytest.mark.django_db
def test_patch_task(client_and_token, configured_project, payload, response, status_code):
    client, token = client_and_token
    task = configured_project.tasks.order_by('-updated_at').all()[0]
    payload['project'] = configured_project.id

    r = client.patch(
        f'/api/tasks/{task.id}/',
        data=json.dumps(payload),
        content_type='application/json',
        headers={'Authorization': f'Token {token}'}
    )

    task = configured_project.tasks.order_by('-updated_at').all()[0]  # call DB again after update
    response['project'] = configured_project.id
    response['created_at'] = task.created_at.isoformat().replace('+00:00', 'Z')
    response['updated_at'] = task.updated_at.isoformat().replace('+00:00', 'Z')
    response['id'] = task.id
    response['total_annotations'] = 0
    response['cancelled_annotations'] = 0
    response['total_predictions'] = 0

    assert r.status_code == status_code
    if response:
        assert r.json() == response
