import logging
from datetime import datetime

from django.core.handlers.wsgi import WSGIRequest
from django.http import JsonResponse, HttpResponse, HttpResponseNotAllowed
from django.urls import reverse
from django.utils import timezone

from web.api_views.serialization_utils import serialize_datetime, location_to_str, flying_team_to_str, add_column, \
    bool_to_yes_no, get_filters_for_data_table_request
from web.models import Appointment, Study, SubjectColumns, AppointmentColumns, AppointmentList, StudyColumns
from web.models.appointment_list import APPOINTMENT_LIST_GENERIC, APPOINTMENT_LIST_UNFINISHED, \
    APPOINTMENT_LIST_APPROACHING
from web.models.constants import GLOBAL_STUDY_ID
from web.templatetags.filters import display_visit_number
from web.views.notifications import get_filter_locations, get_today_midnight_date, get_unfinished_appointments

logger = logging.getLogger(__name__)


def get_appointment_columns(request, appointment_list_type):
    study = Study.objects.filter(id=GLOBAL_STUDY_ID)[0]
    appointment_lists = AppointmentList.objects.filter(study=study, type=appointment_list_type)
    if len(appointment_lists) > 0:
        appointment_list = appointment_lists[0]
        subject_columns = appointment_list.visible_subject_columns
        subject_study_columns = appointment_list.visible_study_subject_columns
        appointment_columns = appointment_list.visible_appointment_columns
    else:
        subject_columns = SubjectColumns()
        subject_study_columns = StudyColumns()
        appointment_columns = AppointmentColumns()

    result = []
    add_column(result, "First name", "first_name", subject_columns, "string_filter")
    add_column(result, "Last name", "last_name", subject_columns, "string_filter")
    add_column(result, "Subject number", "nd_number", subject_study_columns, "string_filter")
    add_column(result, "Type", "type", subject_study_columns, "type_filter")
    add_column(result, "Info sent", "post_mail_sent", appointment_columns, "yes_no_filter")
    add_column(result, "Date", "datetime_when", appointment_columns, None)
    add_column(result, "Appointment types", "appointment_types", appointment_columns, "appointment_type_filter",
               sortable=False)
    add_column(result, "Edit", "edit", None, None, sortable=False)

    return JsonResponse({"columns": result})


def get_appointments(request, appointment_type, min_date, max_date):
    if appointment_type == APPOINTMENT_LIST_GENERIC:
        result = Appointment.objects.filter(location__in=get_filter_locations(request.user))
    elif appointment_type == APPOINTMENT_LIST_UNFINISHED:
        result = get_unfinished_appointments(request.user)
    elif appointment_type == APPOINTMENT_LIST_APPROACHING:
        result = Appointment.objects.filter(
            datetime_when__gt=get_today_midnight_date(),
            location__in=get_filter_locations(request.user),
            status=Appointment.APPOINTMENT_STATUS_SCHEDULED
        ).order_by("datetime_when")
    else:
        raise TypeError("Unknown query type: " + appointment_type)

    if min_date is not None:
        min_date = datetime.strptime(min_date, "%Y-%m-%d").replace(tzinfo=timezone.now().tzinfo)
        result = result.filter(datetime_when__gt=min_date)

    if max_date is not None:
        max_date = datetime.strptime(max_date, "%Y-%m-%d").replace(tzinfo=timezone.now().tzinfo)
        result = result.filter(datetime_when__lt=max_date)
    return result.order_by("datetime_when")


def get_appointments_order(appointments_to_be_ordered, order_column: str, order_direction: str):
    result = appointments_to_be_ordered
    if order_direction == "asc":
        order_direction = ""
    else:
        order_direction = "-"
    if order_column == "first_name":
        result = appointments_to_be_ordered.order_by(order_direction + 'visit__subject__subject__first_name')
    elif order_column == "last_name":
        result = appointments_to_be_ordered.order_by(order_direction + 'visit__subject__subject__last_name')
    elif order_column == "nd_number":
        result = appointments_to_be_ordered.order_by(order_direction + 'visit__subject__nd_number')
    elif order_column == "type":
        result = appointments_to_be_ordered.order_by(order_direction + 'visit__subject__type')
    elif order_column == "location":
        result = appointments_to_be_ordered.order_by(order_direction + 'location')
    elif order_column == "flying_team":
        result = appointments_to_be_ordered.order_by(order_direction + 'flying_team')
    elif order_column == "post_mail_sent":
        result = appointments_to_be_ordered.order_by(order_direction + 'post_mail_sent')
    elif order_column == "datetime_when":
        result = appointments_to_be_ordered.order_by(order_direction + 'datetime_when')
    elif order_column == "subject":
        result = appointments_to_be_ordered.order_by(order_direction + 'visit__subject__subject__last_name',
                                                     order_direction + 'visit__subject__subject__first_name')
    else:
        logger.warning("Unknown sort column: %s", str(order_column))
    return result


def get_appointments_filtered(appointments_to_be_filtered, filters):
    result = appointments_to_be_filtered
    for row in filters:
        column = row[0]
        value = row[1]
        if column == "first_name":
            result = result.filter(visit__subject__subject__first_name__icontains=value)
        elif column == "last_name":
            result = result.filter(visit__subject__subject__last_name__icontains=value)
        elif column == "nd_number":
            result = result.filter(visit__subject__nd_number__icontains=value)
        elif column == "type":
            result = result.filter(visit__subject__type=value)
        elif column == "location":
            result = result.filter(location=value)
        elif column == "flying_team":
            result = result.filter(flying_team=value)
        elif column == "appointment_types":
            result = result.filter(appointment_types=value)
        else:
            message = "UNKNOWN filter: "
            if column is None:
                message += "[None]"
            else:
                message += str(column)
            logger.warning(message)

    return result


def appointments(request: WSGIRequest, appointment_type: str) -> HttpResponse:
    # id of the query from dataTable: https://datatables.net/manual/server-side
    if request.method == "GET":
        request_data = request.GET
    elif request.method == "POST":
        request_data = request.POST
    else:
        return HttpResponseNotAllowed(['GET', 'POST'])

    draw = int(request_data.get("draw", "-1"))

    start = int(request_data.get("start", "0"))
    length = int(request_data.get("length", "10"))

    order = int(request_data.get("order[0][column]", "0"))
    order_dir = request_data.get("order[0][dir]", "asc")
    order_column = request_data.get("columns[" + str(order) + "][data]", "last_name")

    min_date = request_data.get("start_date", None)
    max_date = request_data.get("end_date", None)

    filters = get_filters_for_data_table_request(request_data)

    if min_date is not None:
        length = 1000000000

    all_appointments = get_appointments(request, appointment_type, min_date, max_date)

    count = all_appointments.count()

    sorted_appointments = get_appointments_order(all_appointments, order_column, order_dir)
    filtered_appointments = get_appointments_filtered(sorted_appointments, filters)
    sliced_appointments = filtered_appointments[start:(start + length)]

    result_appointments = sliced_appointments

    count_filtered = all_appointments.count()

    data = []
    for appointment in result_appointments:
        data.append(serialize_appointment(appointment))

    return JsonResponse({
        "draw": draw,
        "recordsTotal": count,
        "recordsFiltered": count_filtered,
        "data": data,
    })


def serialize_appointment(appointment: Appointment):
    subject_string = ""
    first_name = ""
    last_name = ""
    subject_type = ""
    nd_number = screening_number = phone_numbers = appointment_type_names = None
    status = appointment.status
    if appointment.visit is not None:
        title = f"Visit {display_visit_number(appointment.visit.visit_number)}"
        study_subject = appointment.visit.subject
        subject_string = study_subject.subject.last_name + " " + study_subject.subject.first_name
        first_name = study_subject.subject.first_name
        last_name = study_subject.subject.last_name
        nd_number = study_subject.nd_number
        screening_number = study_subject.screening_number
        subject_type = study_subject.type.name
        phone_numbers = ", ".join([_f for _f in [study_subject.subject.phone_number,
                                                 study_subject.subject.phone_number_2,
                                                 study_subject.subject.phone_number_3] if _f])
        appointment_type_names = ", ".join(
            [str(appointment_type_codes) for appointment_type_codes in appointment.appointment_types.all()])
    else:
        title = appointment.comment

    appointment_type_codes = ", ".join(
        [appointment_type_codes.code for appointment_type_codes in appointment.appointment_types.all()])
    until = serialize_datetime(appointment.datetime_until())

    location = location_to_str(appointment.location)
    flying_team = flying_team_to_str(appointment.flying_team)

    worker_assigned = None
    if appointment.worker_assigned is not None:
        worker_assigned = str(appointment.worker_assigned)

    result = {
        "status": status,
        "subject": subject_string,
        "title": title,
        "nd_number": nd_number,
        "screening_number": screening_number,
        "type": subject_type,
        "phone_number": phone_numbers,
        "appointment_type_names": appointment_type_names,
        "datetime_until": until,
        "comment": appointment.comment,
        "color": appointment.color(),
        "id": appointment.id,

        "first_name": first_name,
        "last_name": last_name,
        "location": location,
        "flying_team": flying_team,
        "worker_assigned": worker_assigned,
        "post_mail_sent": bool_to_yes_no(appointment.post_mail_sent),
        "datetime_when": serialize_datetime(appointment.datetime_when),
        "appointment_types": appointment_type_codes,
        "url": reverse('web.views.appointment_edit', kwargs={'appointment_id': str(appointment.id)})
    }
    return result