# coding=utf-8
import datetime
import json
import logging

from django.contrib.auth.models import User
from django.test import Client
from django.test import TestCase
from django.urls import reverse

from web.api_views.subject import get_subjects_order, get_subjects_filtered, serialize_subject
from web.models import StudySubject, Appointment, Study
from web.models.constants import GLOBAL_STUDY_ID, SUBJECT_TYPE_CHOICES_PATIENT, SUBJECT_TYPE_CHOICES_CONTROL
from web.models.study_subject_list import SUBJECT_LIST_GENERIC, SUBJECT_LIST_NO_VISIT, SUBJECT_LIST_REQUIRE_CONTACT, \
    StudySubjectList
from web.tests.functions import create_study_subject, create_worker, create_get_suffix, create_visit, \
    create_appointment, create_empty_study_columns, create_contact_attempt, create_flying_team
from web.views.notifications import get_today_midnight_date

logger = logging.getLogger(__name__)


class TestApi(TestCase):
    def setUp(self):
        self.study_subject = create_study_subject()
        self.client = Client()
        username = 'piotr'
        password = 'top_secret'
        self.user = User.objects.create_user(
            username=username, email='jacob@bla', password=password)
        self.worker = create_worker(self.user)
        self.client.login(username=username, password=password)

    def test_cities(self):
        city_name = "some city"

        response = self.client.get(reverse('web.api.cities'))
        self.assertEqual(response.status_code, 200)

        cities = json.loads(response.content)['cities']

        self.assertFalse(city_name in cities)

        self.study_subject.subject.city = city_name
        self.study_subject.subject.save()

        response = self.client.get(reverse('web.api.cities'))
        cities = json.loads(response.content)['cities']

        self.assertTrue(city_name in cities)

    def test_get_columns(self):
        response = self.client.get(
            reverse('web.api.subjects.columns', kwargs={'subject_list_type': SUBJECT_LIST_GENERIC}))
        self.assertEqual(response.status_code, 200)

        columns = json.loads(response.content)['columns']
        self.assertTrue(len(columns) >= 20)

    def test_get_columns_for_require_contact(self):
        response = self.client.get(
            reverse('web.api.subjects.columns', kwargs={'subject_list_type': SUBJECT_LIST_REQUIRE_CONTACT}))
        self.assertEqual(response.status_code, 200)

        columns = json.loads(response.content)['columns']
        visible_columns = 0
        for column in columns:
            if column["visible"]:
                visible_columns += 1
        self.assertTrue(visible_columns < 20)

    def test_get_columns_when_no_list_is_available(self):
        StudySubjectList.objects.all().delete()
        response = self.client.get(
            reverse('web.api.subjects.columns', kwargs={'subject_list_type': SUBJECT_LIST_GENERIC}))
        self.assertEqual(response.status_code, 200)

        columns = json.loads(response.content)['columns']
        self.assertTrue(len(columns) > 0)

    def test_get_columns_when_study_has_no_data_columns(self):
        study = Study.objects.filter(id=GLOBAL_STUDY_ID)[0]
        study.columns = create_empty_study_columns()
        study.save()

        response = self.client.get(
            reverse('web.api.subjects.columns', kwargs={'subject_list_type': SUBJECT_LIST_GENERIC}))
        self.assertEqual(response.status_code, 200)

        columns = json.loads(response.content)['columns']
        self.assertTrue(len(columns) < 20)

    def test_referrals(self):
        referral_name = "some referral"

        response = self.client.get(reverse('web.api.referrals'))
        self.assertEqual(response.status_code, 200)

        referrals = json.loads(response.content)['referrals']

        self.assertFalse(referral_name in referrals)

        self.study_subject.referral = referral_name
        self.study_subject.save()

        response = self.client.get(reverse('web.api.referrals'))
        referrals = json.loads(response.content)['referrals']

        self.assertTrue(referral_name in referrals)

    def test_subjects_general(self):
        response = self.client.get(reverse('web.api.subjects', kwargs={'type': SUBJECT_LIST_GENERIC}))
        self.assertEqual(response.status_code, 200)

    def test_subjects_no_visit(self):
        response = self.client.get(reverse('web.api.subjects', kwargs={'type': SUBJECT_LIST_NO_VISIT}))
        self.assertEqual(response.status_code, 200)

    def test_subjects_require_contact(self):
        response = self.client.get(reverse('web.api.subjects', kwargs={'type': SUBJECT_LIST_REQUIRE_CONTACT}))
        self.assertEqual(response.status_code, 200)

    def test_subjects_invalid(self):
        response = self.client.get(reverse('web.api.subjects', kwargs={'type': "bla"}))
        self.assertEqual(response.status_code, 500)

    def test_subjects_general_search(self):
        name = "Piotrek"
        self.study_subject.subject.first_name = name
        self.study_subject.subject.save()

        params = {
            "columns[0][search][value]": "another_name",
            "columns[0][data]": "first_name"
        }
        url = ("%s" + create_get_suffix(params)) % reverse('web.api.subjects', kwargs={'type': SUBJECT_LIST_GENERIC})
        response = self.client.get(url)
        self.assertEqual(response.status_code, 200)
        self.assertFalse(name in response.content)

        params["columns[0][search][value]"] = name
        url = ("%s" + create_get_suffix(params)) % reverse('web.api.subjects', kwargs={'type': SUBJECT_LIST_GENERIC})
        response = self.client.get(url)
        self.assertEqual(response.status_code, 200)
        self.assertTrue(name in response.content)

    def check_subject_filtered(self, filters, result):
        subjects = get_subjects_filtered(StudySubject.objects.all(), filters)
        self.assertEqual(len(result), subjects.count())
        for index in range(len(result)):
            self.assertEqual(result[index], subjects[index])

    def check_subject_ordered(self, order, result):
        subjects = get_subjects_order(StudySubject.objects.all(), order, "asc")
        self.assertEqual(len(result), subjects.count())
        for index in range(len(result)):
            self.assertEqual(result[index], subjects[index])

        subjects = get_subjects_order(StudySubject.objects.all(), order, "desc")
        length = len(result)
        self.assertEqual(length, subjects.count())
        for index in range(length):
            self.assertEqual(result[length - index - 1], subjects[index])

    def test_subjects_sort_nd_number(self):
        subject = self.study_subject
        subject.nd_number = "PPP"
        subject.save()

        subject2 = create_study_subject(2)
        subject2.nd_number = "QQQ"
        subject2.save()

        self.check_subject_ordered("nd_number", [subject, subject2])

    def test_subjects_sort_id(self):
        subject = self.study_subject

        subject2 = create_study_subject(2)

        self.check_subject_ordered("id", [subject, subject2])

    def test_subjects_sort_date_born(self):
        subject = self.study_subject
        subject.subject.date_born = get_today_midnight_date()
        subject.subject.save()

        subject2 = create_study_subject(2)
        subject2.subject.date_born = get_today_midnight_date() + datetime.timedelta(days=1)
        subject2.subject.save()

        self.check_subject_ordered("date_born", [subject, subject2])

    def test_subjects_sort_contact_on(self):
        subject = self.study_subject
        subject.datetime_contact_reminder = get_today_midnight_date()
        subject.save()

        subject2 = create_study_subject(2)
        subject2.datetime_contact_reminder = get_today_midnight_date() + datetime.timedelta(days=1)
        subject2.save()

        self.check_subject_ordered("datetime_contact_reminder", [subject, subject2])

    def test_subjects_sort_default_location(self):
        subject = self.study_subject

        self.check_subject_ordered("default_location", [subject])

    def test_subjects_sort_flying_team(self):
        subject = self.study_subject
        subject.flying_team = create_flying_team()
        subject.save()

        self.check_subject_ordered("flying_team", [subject])

    def test_subjects_sort_screening_number(self):
        subject = self.study_subject
        subject.screening_number = "PPP"
        subject.save()

        subject2 = create_study_subject(2)
        subject2.screening_number = "QQQ"
        subject2.save()

        self.check_subject_ordered("screening_number", [subject, subject2])

    def test_subjects_sort_last_name(self):
        subject = self.study_subject
        subject.subject.last_name = "XXX"
        subject.subject.save()

        subject2 = create_study_subject(2)
        subject2.subject.last_name = "YYY"
        subject2.subject.save()

        self.check_subject_ordered("last_name", [subject, subject2])

    def test_subjects_sort_referral(self):
        subject = self.study_subject
        subject.referral = "XXX"
        subject.save()

        subject2 = create_study_subject(2)
        subject2.referral = "YYY"
        subject2.save()

        self.check_subject_ordered("referral", [subject, subject2])

    def test_subjects_sort_dead(self):
        study_subject = self.study_subject
        study_subject.subject.dead = True
        study_subject.subject.save()

        study_subject2 = create_study_subject(2)
        study_subject2.subject.dead = False
        study_subject2.subject.save()

        self.check_subject_ordered("dead", [study_subject2, study_subject])

    def test_subjects_sort_resigned(self):
        subject = self.study_subject
        subject.resigned = True
        subject.save()

        subject2 = create_study_subject(2)
        subject2.resigned = False
        subject2.save()

        self.check_subject_ordered("resigned", [subject2, subject])

    def test_subjects_sort_postponed(self):
        subject = self.study_subject
        subject.postponed = True
        subject.save()

        subject2 = create_study_subject(2)
        subject2.postponed = False
        subject2.save()

        self.check_subject_ordered("postponed", [subject2, subject])

    def test_subjects_filter_dead(self):
        subject = self.study_subject.subject
        subject.dead = True
        subject.save()

        study_subject2 = create_study_subject(2)
        study_subject2.subject.dead = False
        study_subject2.subject.save()

        self.check_subject_filtered([["dead", "true"]], [self.study_subject])
        self.check_subject_filtered([["dead", "false"]], [study_subject2])

    def test_subjects_filter_nd_number(self):
        subject = self.study_subject
        subject.nd_number = "PPP"
        subject.save()

        subject2 = create_study_subject(2)
        subject2.nd_number = "QQQ"
        subject2.save()

        self.check_subject_filtered([["nd_number", "P"]], [subject])

    def test_subjects_filter_screening_number(self):
        subject = self.study_subject
        subject.screening_number = "PPP"
        subject.save()

        subject2 = create_study_subject(2)
        subject2.screening_number = "QQQ"
        subject2.save()

        self.check_subject_filtered([["screening_number", "Q"]], [subject2])

    def test_subjects_filter_last_name(self):
        subject = self.study_subject
        subject.subject.last_name = "XXX"
        subject.subject.save()

        subject2 = create_study_subject(2)
        subject2.subject.last_name = "YYY"
        subject2.subject.save()

        self.check_subject_filtered([["last_name", "Q"]], [])

    def test_subjects_filter_resigned(self):
        subject = self.study_subject
        subject.resigned = True
        subject.save()

        subject2 = create_study_subject(2)
        subject2.resigned = False
        subject2.save()

        self.check_subject_filtered([["resigned", "true"]], [subject])
        self.check_subject_filtered([["resigned", "false"]], [subject2])

    def test_subjects_filter_postponed(self):
        subject = self.study_subject
        subject.postponed = True
        subject.save()

        subject2 = create_study_subject(2)
        subject2.postponed = False
        subject2.save()

        self.check_subject_filtered([["postponed", "true"]], [subject])
        self.check_subject_filtered([["postponed", "false"]], [subject2])

    def test_subjects_filter_default_location(self):
        subject = self.study_subject

        self.check_subject_filtered([["default_location", str(subject.default_location.id)]], [subject])
        self.check_subject_filtered([["default_location", "-1"]], [])

    def test_subjects_filter_flying_team(self):
        subject = self.study_subject
        subject.flying_team = create_flying_team()
        subject.save()

        self.check_subject_filtered([["flying_team", str(subject.flying_team.id)]], [subject])
        self.check_subject_filtered([["flying_team", "-1"]], [])

    def test_subjects_filter_information_sent(self):
        subject = self.study_subject
        subject.information_sent = True
        subject.save()

        self.check_subject_filtered([["information_sent", "true"]], [subject])
        self.check_subject_filtered([["information_sent", "false"]], [])

    def test_subjects_filter_referral(self):
        subject = self.study_subject
        subject.referral = "xyz"
        subject.save()

        self.check_subject_filtered([["referral", "xyz"]], [subject])
        self.check_subject_filtered([["referral", "false"]], [])

    def test_subjects_filter_type(self):
        subject = self.study_subject
        subject.type = SUBJECT_TYPE_CHOICES_PATIENT
        subject.save()

        self.check_subject_filtered([["type", SUBJECT_TYPE_CHOICES_PATIENT]], [subject])
        self.check_subject_filtered([["type", SUBJECT_TYPE_CHOICES_CONTROL]], [])

    def test_subjects_filter_unknown(self):
        subject = self.study_subject

        self.check_subject_filtered([["some_unknown", "unknown data"]], [subject])
        self.check_subject_filtered([["", ""]], [subject])
        self.check_subject_filtered([["", None]], [subject])

    def test_serialize_subject(self):
        study_subject = self.study_subject
        study_subject.subject.dead = True
        study_subject.flying_team = create_flying_team()
        study_subject.datetime_contact_reminder = get_today_midnight_date()
        create_contact_attempt(subject=study_subject)
        create_visit(subject=study_subject)

        subject_json = serialize_subject(study_subject)
        self.assertEqual("YES", subject_json["dead"])

    def test_subjects_filter_visit_1_DONE(self):
        subject = self.study_subject

        visit = create_visit(subject)
        appointment = create_appointment(visit)
        appointment.status = Appointment.APPOINTMENT_STATUS_FINISHED
        appointment.save()
        visit.mark_as_finished()

        self.check_subject_filtered([["visit_1", "DONE"]], [subject])
        self.check_subject_filtered([["visit_2", "DONE"]], [])

        self.check_subject_filtered([["visit_1", "MISSED"]], [])
        self.check_subject_filtered([["visit_1", "EXCEED"]], [])
        self.check_subject_filtered([["visit_1", "IN_PROGRESS"]], [])
        self.check_subject_filtered([["visit_1", "SHOULD_BE_IN_PROGRESS"]], [])
        self.check_subject_filtered([["visit_1", "UPCOMING"]], [])

    def test_subjects_filter_visit_1_MISSED(self):
        subject = self.study_subject

        visit = create_visit(subject)
        appointment = create_appointment(visit)
        appointment.status = Appointment.APPOINTMENT_STATUS_CANCELLED
        appointment.save()
        visit.mark_as_finished()

        self.check_subject_filtered([["visit_1", "MISSED"]], [subject])
        self.check_subject_filtered([["visit_2", "MISSED"]], [])

        self.check_subject_filtered([["visit_1", "DONE"]], [])
        self.check_subject_filtered([["visit_1", "EXCEED"]], [])
        self.check_subject_filtered([["visit_1", "IN_PROGRESS"]], [])
        self.check_subject_filtered([["visit_1", "SHOULD_BE_IN_PROGRESS"]], [])
        self.check_subject_filtered([["visit_1", "UPCOMING"]], [])

    def test_subjects_filter_visit_1_EXCEED(self):
        subject = self.study_subject

        visit = create_visit(subject)
        visit.datetime_begin = get_today_midnight_date() + datetime.timedelta(days=-365 * 2)
        visit.datetime_end = get_today_midnight_date() + datetime.timedelta(days=-365 * 1)
        visit.save()

        self.check_subject_filtered([["visit_1", "EXCEED"]], [subject])
        self.check_subject_filtered([["visit_2", "EXCEED"]], [])

        self.check_subject_filtered([["visit_1", "DONE"]], [])
        self.check_subject_filtered([["visit_1", "MISSED"]], [])
        self.check_subject_filtered([["visit_1", "IN_PROGRESS"]], [])
        self.check_subject_filtered([["visit_1", "SHOULD_BE_IN_PROGRESS"]], [])
        self.check_subject_filtered([["visit_1", "UPCOMING"]], [])

    def test_subjects_filter_visit_1_IN_PROGRESS(self):
        subject = self.study_subject

        visit = create_visit(subject)
        appointment = create_appointment(visit)
        appointment.status = Appointment.APPOINTMENT_STATUS_SCHEDULED
        appointment.save()

        self.check_subject_filtered([["visit_1", "IN_PROGRESS"]], [subject])
        self.check_subject_filtered([["visit_2", "IN_PROGRESS"]], [])

        self.check_subject_filtered([["visit_1", "DONE"]], [])
        self.check_subject_filtered([["visit_1", "MISSED"]], [])
        self.check_subject_filtered([["visit_1", "EXCEED"]], [])
        self.check_subject_filtered([["visit_1", "SHOULD_BE_IN_PROGRESS"]], [])
        self.check_subject_filtered([["visit_1", "UPCOMING"]], [])

    def test_subjects_filter_visit_1_SHOULD_BE_IN_PROGRESS(self):
        subject = self.study_subject

        create_visit(subject)

        self.check_subject_filtered([["visit_1", "SHOULD_BE_IN_PROGRESS"]], [subject])
        self.check_subject_filtered([["visit_2", "SHOULD_BE_IN_PROGRESS"]], [])

        self.check_subject_filtered([["visit_1", "DONE"]], [])
        self.check_subject_filtered([["visit_1", "MISSED"]], [])
        self.check_subject_filtered([["visit_1", "EXCEED"]], [])
        self.check_subject_filtered([["visit_1", "IN_PROGRESS"]], [])
        self.check_subject_filtered([["visit_1", "UPCOMING"]], [])

    def test_subjects_filter_visit_1_UPCOMING(self):
        subject = self.study_subject

        visit = create_visit(subject)
        visit.datetime_begin = get_today_midnight_date() + datetime.timedelta(days=2)
        visit.datetime_end = get_today_midnight_date() + datetime.timedelta(days=20)
        visit.save()

        self.check_subject_filtered([["visit_1", "UPCOMING"]], [subject])
        self.check_subject_filtered([["visit_2", "UPCOMING"]], [])

        self.check_subject_filtered([["visit_1", "DONE"]], [])
        self.check_subject_filtered([["visit_1", "MISSED"]], [])
        self.check_subject_filtered([["visit_1", "EXCEED"]], [])
        self.check_subject_filtered([["visit_1", "IN_PROGRESS"]], [])
        self.check_subject_filtered([["visit_1", "SHOULD_BE_IN_PROGRESS"]], [])

    def test_subjects_filter_visit_1_visit_2_combined(self):
        subject = self.study_subject

        visit = create_visit(subject)
        appointment = create_appointment(visit)
        appointment.status = Appointment.APPOINTMENT_STATUS_FINISHED
        appointment.save()
        visit.mark_as_finished()

        self.check_subject_filtered([["visit_1", "DONE"]], [subject])
        self.check_subject_filtered([["visit_2", "UPCOMING"]], [subject])

        self.check_subject_filtered([["visit_1", "DONE"], ["visit_2", "UPCOMING"]], [subject])
        self.check_subject_filtered([["visit_1", "UPCOMING"], ["visit_2", "DONE"]], [])

    def test_subjects_ordered_by_visit_1(self):
        subject = self.study_subject
        subject2 = create_study_subject(2)

        visit = create_visit(subject)
        appointment = create_appointment(visit)
        appointment.status = Appointment.APPOINTMENT_STATUS_FINISHED
        appointment.save()

        self.check_subject_ordered("visit_1", [subject, subject2])

    def test_invalid_order(self):
        self.check_subject_ordered(None, [self.study_subject])

    def test_subjects_ordered_by_contact_attempt(self):
        subject = self.study_subject
        subject2 = create_study_subject(2)

        create_contact_attempt(subject=subject)

        self.check_subject_ordered("last_contact_attempt", [subject, subject2])