import re
from datetime import timedelta
from itertools import chain
from logging import getLogger
from typing import Iterable

from NEMO.forms import nice_errors
from NEMO.models import Comment, Task, Tool, UsageEvent, User
from NEMO.utilities import (
    export_format_datetime,
    extract_optional_beginning_and_end_times,
    format_datetime,
    render_combine_responses,
)
from NEMO.views.pagination import SortedPaginator
from NEMO.views.tool_control import tool_status as original_tool_status
from django.conf import settings
from django.contrib import messages
from django.contrib.auth.decorators import login_required
from django.http import HttpResponse, HttpResponseBadRequest, HttpResponseForbidden
from django.shortcuts import get_object_or_404, redirect, render
from django.utils import timezone
from django.utils.html import strip_tags
from django.views.decorators.http import require_GET, require_POST, require_http_methods

from NEMO_osticket.customizations import OsTicketCustomization
from NEMO_osticket.exceptions import OsTicketException
from NEMO_osticket.forms import OsTicketForm, OsTicketSearchForm
from NEMO_osticket.models import OstThreadEntryType, OstTicket
from NEMO_osticket.osticket_api import (
    OsTicketSearchObject,
    add_reply_to_ticket,
    create_os_ticket,
    get_os_ticket_service,
    get_preloaded_os_tickets,
    get_ticket_form_field_labels,
)

osticket_logger = getLogger(__name__)


@login_required
@require_GET
def tickets(request):
    user: User = request.user
    search_form = OsTicketSearchForm(request.GET or None, initial={"email": user.email, "is_open": True})
    ticket_field_labels = get_ticket_form_field_labels()
    dictionary = {
        "form": search_form,
        "search_done": request.GET,
        "page": [],
        "ticket_field_labels": ticket_field_labels,
    }

    if search_form.is_valid():
        matching_tickets = search_form.get_search_object().search_tickets(preload=False)
        ticket_list = SortedPaginator(matching_tickets, request, order_by="-created").get_current_page()
        dictionary["page"] = ticket_list
        dictionary["tickets"] = get_preloaded_os_tickets(
            [ticket.ticket_id for ticket in ticket_list], order_by=ticket_list.paginator.order_by
        )
    return render(request, "NEMO_osticket/tickets/ticket_search.html", dictionary)


@login_required
@require_GET
def ticket_details(request, ticket_id):
    get_object_or_404(OstTicket, pk=ticket_id)
    loaded_tickets = get_preloaded_os_tickets([ticket_id])
    ticket_field_labels = get_ticket_form_field_labels()
    dictionary = {
        "ticket": next(iter(loaded_tickets)) if loaded_tickets else None,
        "ticket_field_labels": ticket_field_labels,
    }
    return render(request, "NEMO_osticket/ticket_details/ticket_details.html", dictionary)


@login_required
@require_GET
def tool_status(request, tool_id):
    original_response = original_tool_status(request, tool_id)
    no_osticket = not get_os_ticket_service().get("available", False) and not settings.DATABASES.get("osticket", False)
    if no_osticket or original_response.status_code != 200:
        return original_response

    dictionary = {
        "tool_id": tool_id,
        "replace_problems_tab": OsTicketCustomization.get_bool("osticket_tool_control_replace_problem_tab"),
        "tab_name": OsTicketCustomization.get("osticket_tool_control_tab_name"),
    }

    return render_combine_responses(
        request, original_response, "NEMO_osticket/tool_control/osticket_tab.html", dictionary
    )


@login_required
@require_GET
def tool_status_tickets(request, tool_id):
    tool = get_object_or_404(Tool, pk=tool_id)
    create_ticket_include_usage_events = OsTicketCustomization.get_bool("osticket_create_ticket_include_usage_events")
    past_week_usage_events = UsageEvent.objects.none()
    if create_ticket_include_usage_events:
        # since a week ago
        start = timezone.now() - timedelta(days=7)
        past_week_usage_events = UsageEvent.objects.filter(
            tool_id=tool_id, start__gte=start, end__isnull=False
        ).order_by("-start")
        past_week_usage_events = past_week_usage_events.prefetch_related("user")

    dictionary = {
        "usage_events": past_week_usage_events,
        "osticket_api_available": get_os_ticket_service().get("available", False),
        "tickets": OsTicketSearchObject(tool=tool, is_open=True).search_tickets(),
        "form": OsTicketForm(),
        "tool_id": tool_id,
        "ticket_field_labels": get_ticket_form_field_labels(),
    }
    return render(request, "NEMO_osticket/tool_control/osticket_tab_content.html", dictionary)


@login_required
@require_http_methods(["GET", "POST"])
def create_ticket(request):
    form = OsTicketForm(request.POST or None, include_usage_events=False)
    dictionary = {"form": form}
    if form.is_valid():
        try:
            create_os_ticket(request, form.cleaned_data, None)
            messages.success(request, "Your ticket was successfully created")
            return redirect("osticket_tickets")
        except OsTicketException as e:
            osticket_logger.exception(e)
            form.add_error(None, f"There was an error creating the ticket: {str(e)}")
    return render(request, "NEMO_osticket/tickets/create_ticket.html", dictionary)


@login_required
@require_POST
def create_tool_ticket(request, tool_id):
    tool = get_object_or_404(Tool, pk=tool_id)

    form = OsTicketForm(request.POST)
    if not form.is_valid():
        return HttpResponseBadRequest(nice_errors(form).as_ul())

    try:
        create_os_ticket(request, form.cleaned_data, tool)
        messages.success(request, "Your ticket was successfully created")
    except OsTicketException as e:
        osticket_logger.exception(e)
        messages.error(request, f"There was an error creating the ticket: {str(e)}")

    return redirect("tool_control")


@login_required
@require_POST
def reply_to_ticket(request, ticket_id):
    ticket = get_object_or_404(OstTicket, pk=ticket_id)
    user: User = request.user
    if not user.is_active:
        return HttpResponseForbidden("You are not authorized to reply to this ticket")
    message = request.POST.get("message")
    # Only staff can post internal notes
    internal = request.POST.get("internal", False) == "on" and user.is_any_part_of_staff
    if message:
        add_reply_to_ticket(request, ticket, user, message, internal)
    return redirect("osticket_ticket_details", ticket_id=ticket_id)


@login_required
@require_GET
def past_comments_and_tasks(request):
    user: User = request.user
    start, end = extract_optional_beginning_and_end_times(request.GET)
    search = request.GET.get("search")
    if not start and not end and not search:
        return HttpResponseBadRequest("Please enter a search keyword, start date or end date.")
    tool = get_object_or_404(Tool, pk=request.GET["tool_id"])
    search_object = OsTicketSearchObject(**{"tool": tool, "search": search, "start": start, "end": end})
    try:
        matching_tickets = search_object.search_tickets()
        tasks = Task.objects.filter(tool_id=tool.id)
        comments = Comment.objects.filter(tool_id=tool.id)
        if not user.is_staff:
            comments = comments.filter(staff_only=False)
        if start:
            tasks = tasks.filter(creation_time__gt=start)
            comments = comments.filter(creation_date__gt=start)
        if end:
            tasks = tasks.filter(creation_time__lt=end)
            comments = comments.filter(creation_date__lt=end)
        if search:
            tasks = tasks.filter(problem_description__icontains=search)
            comments = comments.filter(content__icontains=search)
    except:
        osticket_logger.exception("Task and comment lookup failed.")
        return HttpResponseBadRequest("Task and comment lookup failed.")
    past = list(chain(matching_tickets, tasks, comments))
    past.sort(
        key=lambda x: getattr(x, "created", None)
        or getattr(x, "creation_time", None)
        or getattr(x, "creation_date", None)
    )
    past.reverse()
    if request.GET.get("export"):
        return export_comments_and_tasks_to_text(request, past)
    dictionary = {"past": past}
    return render(request, "NEMO_osticket/tool_control/osticket_past_tasks_and_comments.html", dictionary)


@login_required
@require_GET
def ten_most_recent_past_comments_and_tasks(request, tool_id):
    user: User = request.user
    tool = get_object_or_404(Tool, pk=tool_id)
    matching_tickets = list(OsTicketSearchObject(tool=tool, is_open=True).search_tickets())[:10]
    tasks = Task.objects.filter(tool_id=tool_id).order_by("-creation_time")[:10]
    comments = Comment.objects.filter(tool_id=tool_id).order_by("-creation_date")
    if not user.is_staff:
        comments = comments.filter(staff_only=False)
    comments = comments[:10]
    past = list(chain(matching_tickets, tasks, comments))
    past.sort(
        key=lambda x: getattr(x, "created", None)
        or getattr(x, "creation_time", None)
        or getattr(x, "creation_date", None)
    )
    past.reverse()
    past = past[0:10]
    if request.GET.get("export"):
        return export_comments_and_tasks_to_text(request, past)
    dictionary = {"past": past}
    return render(request, "NEMO_osticket/tool_control/osticket_past_tasks_and_comments.html", dictionary)


def export_comments_and_tasks_to_text(request, comments_and_tasks: Iterable):
    topic_label = OsTicketCustomization.get("osticket_create_ticket_help_topic_label")
    content = "No tickets, tasks or comments were created between these dates." if not comments_and_tasks else ""
    for item in comments_and_tasks:
        if isinstance(item, OstTicket):
            ticket: OstTicket = item
            if ticket.topic_id:
                content += f"{topic_label}: {ticket.topic.topic}\n"
            subject_label_and_value = ticket.form_entry_values.get("subject")
            if subject_label_and_value:
                subject_label, subject_value = subject_label_and_value
                content += f"{subject_label}: {subject_value}\n"
            for thread_entry in ticket.ostthreadentry_set:
                if thread_entry.type != OstThreadEntryType.INTERNAL_NOTE or request.user.is_staff:
                    content += f"\nOn {format_datetime(ticket.created)}\n"
                    content += f"{html_to_text(thread_entry.body)}\n"
        if isinstance(item, Comment):
            comment: Comment = item
            staff_only = "staff only " if comment.staff_only else ""
            content += f"On {format_datetime(comment.creation_date)} {comment.author} wrote this {staff_only}comment:\n"
            content += f"{comment.content}\n"
            if comment.hide_date:
                content += f"{comment.hidden_by} hid the comment on {format_datetime(comment.hide_date)}.\n"
        elif isinstance(item, Task):
            task: Task = item
            content += f"On {format_datetime(task.creation_time)} {task.creator} created this task:\n"
            if task.problem_category:
                content += f"{task.problem_category.name}\n"
            if task.force_shutdown:
                content += "\nThe tool was shut down because of this task.\n"
            if task.progress_description:
                content += f"\n{task.progress_description}\n"
            if task.resolved:
                resolution_category = f"({task.resolution_category}) " if task.resolution_category else ""
                content += (
                    f"\nResolved {resolution_category}On {format_datetime(task.resolution_time)} by {task.resolver }.\n"
                )
                if task.resolution_description:
                    content += f"{task.resolution_description}\n"
            elif task.cancelled:
                content += f"\nCancelled On {format_datetime(task.resolution_time)} by {task.resolver}.\n"
        content += "\n---------------------------------------------------\n\n"
    response = HttpResponse(content, content_type="text/plain")
    response["Content-Disposition"] = "attachment; filename={0}".format(
        f"comments_and_tasks_export_{export_format_datetime()}.txt"
    )
    return response


def html_to_text(html):
    # Replace <br> or <br/> with newline
    text = re.sub(r"<br\s*/?>", "\n", html, flags=re.IGNORECASE)
    # Remove all other HTML tags
    text = strip_tags(text)
    return text


# TODO: add a hook to kiosk as well
