"""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
import math

from django.apps import apps
from django.urls import reverse
from tasks.models import Task, Annotation
from projects.models import Project
from .utils import invite_client_to_project, _client_is_annotator


@pytest.fixture
def configured_project_min_annotations_1(configured_project):
    p = Project.objects.get(id=configured_project.id)
    p.min_annotations_to_start_training = 1
    # p.agreement_method = p.SINGLE
    p.save()
    return p


@pytest.mark.django_db
@pytest.mark.parametrize('result, logtext, ml_upload_called', [
    (json.dumps([{'from_name': 'text_class', 'to_name': 'text', 'type': 'labels', 'value': {'labels': ['class_A'], 'start': 0, 'end': 1}}]), None, True),
    (json.dumps([]), None, True),
])
def test_create_annotation(caplog, any_client, configured_project_min_annotations_1, result, logtext, ml_upload_called):
    task = Task.objects.first()
    if _client_is_annotator(any_client):
        assert invite_client_to_project(any_client, task.project).status_code == 200
    with requests_mock.Mocker() as m:
        m.post('http://localhost:8999/train')
        m.post('http://localhost:8999/webhook')
        annotation = {'result': result, 'task': task.id, 'lead_time': 2.54}
        r = any_client.post(f'/api/tasks/{task.id}/annotations/', data=annotation)
        # check that submitted VALID data for task_annotation
        # makes task labeled
        task.refresh_from_db()
        assert task.is_labeled is True
        assert r.status_code == 201
        annotation = Annotation.objects.all()
        assert annotation.count() == 1
        annotation = annotation.first()
        assert annotation.task.id == task.id
        # annotator client
        if hasattr(any_client, 'annotator') and any_client.annotator is not None:
            assert annotation.completed_by.id == any_client.user.id
            assert annotation.updated_by.id == any_client.user.id
        # business client
        else:
            assert annotation.completed_by.id == any_client.business.admin.id
            assert annotation.updated_by.id == any_client.business.admin.id

        if apps.is_installed('businesses'):
            assert annotation.task.accuracy == 1.0

        if logtext:
            assert logtext in caplog.text


@pytest.mark.django_db
def test_create_annotation_with_ground_truth(caplog, any_client, configured_project_min_annotations_1):

    task = Task.objects.first()
    client_is_annotator = _client_is_annotator(any_client)
    if client_is_annotator:
        assert invite_client_to_project(any_client, task.project).status_code == 200

    webhook_called = not client_is_annotator
    ground_truth = {
        'task': task.id,
        'result': json.dumps([{'from_name': 'text_class', 'to_name': 'text', 'value': {'labels': ['class_A'], 'start': 0, 'end': 1}}]),
        'ground_truth': True
    }

    annotation = {
        'task': task.id,
        'result': json.dumps([{'from_name': 'text_class', 'to_name': 'text', 'value': {'labels': ['class_B'], 'start': 0, 'end': 1}}])
    }

    with requests_mock.Mocker() as m:
        m.post('http://localhost:8999/webhook')
        m.post('http://localhost:8999/train')

        # ground_truth doesn't affect statistics & ML backend, webhook is called for admin accounts
        r = any_client.post('/api/tasks/{}/annotations/'.format(task.id), data=ground_truth)
        assert r.status_code == 201
        assert m.called == webhook_called

        # real annotation triggers uploading to ML backend and recalculating accuracy
        r = any_client.post('/api/tasks/{}/annotations/'.format(task.id), data=annotation)
        assert r.status_code == 201
        assert m.called
        task = Task.objects.get(id=task.id)
        assert task.annotations.count() == 2
        annotations = Annotation.objects.filter(task=task)
        for a in annotations:
            assert a.updated_by.id == any_client.user.id


@pytest.mark.django_db
def test_delete_annotation(business_client, configured_project):
    task = Task.objects.first()
    annotation = Annotation.objects.create(task=task, project=configured_project, result=[])
    assert task.annotations.count() == 1
    r = business_client.delete('/api/annotations/{}/'.format(annotation.id))
    assert r.status_code == 204
    assert task.annotations.count() == 0


@pytest.fixture
def annotations():
    task = Task.objects.first()
    return {
        'class_A': {
            'task': task.id,
            'result': json.dumps([{'from_name': 'text_class', 'to_name': 'text', 'type': 'labels', 'value': {
                'labels': ['class_A'], 'start': 0, 'end': 10
            }}])
        },
        'class_B': {
            'task': task.id,
            'result': json.dumps([{'from_name': 'text_class', 'to_name': 'text', 'type': 'labels', 'value': {
                'labels': ['class_B'], 'start': 0, 'end': 10
            }}])
        },
        'empty': {
            'task': task.id,
            'result': json.dumps([])
        }
    }


@pytest.fixture
def project_with_max_annotations_2(configured_project):
    configured_project.maximum_annotations = 2
    # configured_project.agreement_method = Project.SINGLE
    configured_project.save()


@pytest.mark.parametrize('annotations_sequence, accuracy, is_labeled', [
    ([], None, False),
    ([('class_A', 'business')], 1, False),
    ([('class_A', 'annotator')], 1, False),
    ([('class_A', 'business'), ('class_A', 'business')], 1, True),
    ([('class_A', 'business'), ('class_A', 'annotator')], 1, True),
    ([('class_A', 'annotator'), ('class_A', 'business')], 1, True),
    ([('class_A', 'business'), ('class_B', 'business')], 0.5, True),
    ([('class_A', 'business'), ('class_B', 'annotator')], 0.5, True),
    ([('class_A', 'annotator'), ('class_B', 'business')], 0.5, True),
    ([('empty', 'annotator'), ('empty', 'business')], 1, True),
    ([('class_A', 'annotator'), ('empty', 'business')], 0.5, True)
])
@pytest.mark.django_db
def test_accuracy(
        business_client, annotator_client, project_with_max_annotations_2, annotations, annotations_sequence,
        accuracy, is_labeled
):
    client = {
        'business': business_client,
        'annotator': annotator_client
    }
    task_id = next(iter(annotations.values()))['task']
    task = Task.objects.get(id=task_id)
    invite_client_to_project(annotator_client, task.project)

    for annotation_key, client_key in annotations_sequence:
        r = client[client_key].post(
            reverse('tasks:api:task-annotations', kwargs={'pk': task_id}), data=annotations[annotation_key])
        assert r.status_code == 201
    task = Task.objects.get(id=task_id)
    assert task.is_labeled == is_labeled


@pytest.mark.django_db
def test_accuracy_on_delete(business_client, project_with_max_annotations_2, annotations):
    task_id = next(iter(annotations.values()))['task']
    for annotation in annotations.values():
        business_client.post(reverse('tasks:api:task-annotations', kwargs={'pk': task_id}), data=annotation)

    task = Task.objects.get(id=task_id)
    assert task.annotations.count() == len(annotations)
    assert task.is_labeled
    annotation_ids = [c.id for c in task.annotations.all()]
    r = business_client.delete(reverse('tasks:api-annotations:annotation-detail', kwargs={'pk': annotation_ids[0]}))
    assert r.status_code == 204
    task = Task.objects.get(id=task_id)
    assert task.is_labeled  # project.max_annotations = 2

    r = business_client.delete(reverse('tasks:api-annotations:annotation-detail', kwargs={'pk': annotation_ids[1]}))
    assert r.status_code == 204
    task = Task.objects.get(id=task_id)
    assert not task.is_labeled

    r = business_client.delete(reverse('tasks:api-annotations:annotation-detail', kwargs={'pk': annotation_ids[2]}))
    assert r.status_code == 204
    task = Task.objects.get(id=task_id)
    assert not task.is_labeled


# @pytest.mark.django_db
# def test_accuracy_on_delete(business_client, project_with_max_annotations_2, annotations):
#     task_id = next(iter(annotations.values()))['task']
#     for annotation in annotations.values():
#         business_client.post(reverse('tasks:api:task-annotations', kwargs={'pk': task_id}), data=annotation)
#
#     task = Task.objects.get(id=task_id)
#     assert task.annotations.count() == len(annotations)
#     if apps.is_installed('businesses'):
#         assert math.fabs(task.accuracy - 1 / 3) < 0.00001
#     assert task.is_labeled
#     annotation_ids = [c.id for c in task.annotations.all()]
#     r = business_client.delete(reverse('tasks:api-annotations:annotation-detail', kwargs={'pk': annotation_ids[0]}))
#     assert r.status_code == 204
#     task = Task.objects.get(id=task_id)
#     if apps.is_installed('businesses'):
#         assert task.accuracy == 0.5
#     assert task.is_labeled  # project.max_annotations = 2
#
#     r = business_client.delete(reverse('tasks:api-annotations:annotation-detail', kwargs={'pk': annotation_ids[1]}))
#     assert r.status_code == 204
#     task = Task.objects.get(id=task_id)
#     if apps.is_installed('businesses'):
#         assert task.accuracy == 1.0
#     assert not task.is_labeled
#
#     r = business_client.delete(reverse('tasks:api-annotations:annotation-detail', kwargs={'pk': annotation_ids[2]}))
#     assert r.status_code == 204
#     task = Task.objects.get(id=task_id)
#     if apps.is_installed('businesses'):
#         assert task.accuracy is None
#     assert not task.is_labeled
