# -*- coding: utf-8 -*-

import mimetypes
import os
import six

try:
    import json
except ImportError:
    import simplejson as json

import django.contrib.auth.views
from django.conf import settings
from django.contrib.auth import REDIRECT_FIELD_NAME, get_user_model
from django.core.exceptions import ImproperlyConfigured
from django.http import HttpResponse, HttpResponseNotFound, StreamingHttpResponse, HttpResponseForbidden
from django.shortcuts import render, get_object_or_404
from django.template import RequestContext
from django.urls import reverse
from django.views.generic import RedirectView

from kobo.hub.models import Arch, Channel, Task
from kobo.hub.forms import TaskSearchForm
from kobo.django.views.generic import ExtraDetailView, SearchView, UsersAclMixin
from kobo.django.compat import gettext_lazy as _


# max log size returned in HTML-embedded view
HTML_LOG_MAX_SIZE = getattr(settings, "HTML_LOG_MAX_SIZE", (1024 ** 2) * 2)

# max log size returned in a JSON request
JSON_LOG_MAX_SIZE = getattr(settings, "JSON_LOG_MAX_SIZE", (1024 ** 2) * 8)

# default LogWatcher JS poll interval
LOG_WATCHER_INTERVAL = getattr(settings, "LOG_WATCHER_INTERVAL", 5000)


class UserDetailView(UsersAclMixin, ExtraDetailView):
    model = get_user_model()
    title = _("User detail")
    template_name = "user/detail.html"
    context_object_name = "usr"

    def get_context_data(self, **kwargs):
        context = super(UserDetailView, self).get_context_data(**kwargs)
        context['tasks'] = kwargs['object'].task_set.count()
        return context

class DetailViewWithWorkers(ExtraDetailView):
    model = Channel
    template_name = "channel/detail.html"
    context_object_name = "channel"
    title = _("Architecture detail")

    def get_context_data(self, **kwargs):
        context = super(DetailViewWithWorkers, self).get_context_data(**kwargs)
        context["worker_list"] = kwargs["object"].worker_set.order_by("name")
        return context

class ArchDetailView(ExtraDetailView):
    model = Arch
    template_name = "arch/detail.html"
    context_object_name = "arch"
    title = _("Architecture detail")

    def get_context_data(self, **kwargs):
        context = super(ArchDetailView, self).get_context_data(**kwargs)
        context["worker_list"] = kwargs["object"].worker_set.order_by("name")
        return context

class TaskListView(SearchView):
    # TODO: missing kwargs custom queries for backward compatibility
    title = _("All tasks")
    model = Task
    form_class = TaskSearchForm
    template_name = "task/list.html"
    context_object_name = "task_list"
    state = None
    order_by = ['-id']

    def get_form_kwargs(self):
        kwargs = super(TaskListView, self).get_form_kwargs()
        kwargs['state'] = self.state
        kwargs['order_by'] = self.order_by
        return kwargs


class TaskDetail(ExtraDetailView):
    queryset = Task.objects.select_related()
    context_object_name = "task"
    template_name = "task/detail.html"
    title = _("Task detail")

    def get_context_data(self, **kwargs):
        context = super(TaskDetail, self).get_context_data(**kwargs)
        logs = []
        for i in kwargs['object'].logs.list:
            if self.request.user.has_perm('hub.can_see_traceback'):
                logs.append(i)
                continue
            if not os.path.basename(i).startswith("traceback"):
                logs.append(i)
        logs.sort()
        context["logs"] = logs
        context['task_list'] = kwargs['object'].subtasks()
        return context


def _stream_file(f, offset=0):
    """Generator that returns 1M file chunks."""
    f.seek(offset)
    while 1:
        data = f.read(1024 ** 2)
        if not data:
            break
        yield data
    f.close()


def _trim_log(text):
    # break at first line if possible
    nl = text.find('\n')
    if nl > 0:
        subtext = text[nl:]
    else:
        subtext = '\n' + text
    return '<...trimmed, download required for full log>' + subtext


def _streamed_log_response(task, log_name, offset, as_attachment):
    file_path = task.logs._get_absolute_log_path(log_name)
    if not os.path.isfile(file_path) and not file_path.endswith(".gz"):
        file_path = task.logs._get_absolute_log_path(log_name + ".gz")

    mimetype = mimetypes.guess_type(file_path)[0] or 'application/octet-stream'

    try:
        content_len = os.path.getsize(file_path) - offset
    except OSError:
        content_len = 0

    try:
        f = open(file_path, "rb")
    except FileNotFoundError:
        return HttpResponseNotFound('Cannot find file ' + log_name)

    # use _stream_file() instead of passing file object in order to improve performance
    response = StreamingHttpResponse(_stream_file(f, offset), content_type=mimetype)
    response["Content-Length"] = content_len

    if as_attachment:
        # set filename to be real filesystem name
        response['Content-Disposition'] = 'attachment; filename=%s' % os.path.basename(file_path)

    return response


def _rendered_log_response(request, task, log_name):
    exts = getattr(settings, "VALID_TASK_LOG_EXTENSIONS", [".log"])
    found = False
    for ext in exts:
        if log_name.endswith(ext):
            found = True
    if not found:
        return HttpResponseForbidden("Can display only specific file types: %s" % ", ".join(exts))

    (content, offset) = task.logs.tail(log_name, HTML_LOG_MAX_SIZE)

    # Add "trimmed" message if tail did not return entire log file.
    if len(content) < offset:
        content = b'<...trimmed, download required for full log>\n' + content

    content = content.decode("utf-8", "replace")

    context = {
        "title": "Task log",
        "offset": offset,
        "task_finished": task.is_finished() and 1 or 0,
        "next_poll": None if task.is_finished() else LOG_WATCHER_INTERVAL,
        "content": content,
        "log_name": log_name,
        "task": task,
        "json_url": reverse("task/log-json", args=[task.id, log_name]),
    }

    return render(request, "task/log.html", context)


def task_log(request, id, log_name):
    """
    IMPORTANT: reverse to 'task/log-json' *must* exist
    """
    if os.path.basename(log_name).startswith("traceback") and not request.user.has_perm('hub.can_see_traceback'):
        return HttpResponseForbidden("You don't have permission to see the traceback.")

    task = get_object_or_404(Task, id=id)

    offset = int(request.GET.get("offset", 0))

    request_format = request.GET.get("format")

    if request_format == "raw" or log_name.endswith(".html") or log_name.endswith(".htm"):
        return _streamed_log_response(task, log_name, offset, as_attachment=(request_format == 'raw'))

    return _rendered_log_response(request, task, log_name)


def task_log_json(request, id, log_name):
    if os.path.basename(log_name).startswith("traceback") and not request.user.is_superuser:
        return HttpResponseForbidden(content_type="application/json")

    task = get_object_or_404(Task, id=id)
    offset = int(request.GET.get("offset", 0))
    content = task.logs.get_chunk(log_name, offset, JSON_LOG_MAX_SIZE + 5)

    if len(content) > JSON_LOG_MAX_SIZE:
        # We immediately have more log content to read
        next_poll = 0
        content = content[:JSON_LOG_MAX_SIZE]
    elif task.is_finished():
        # There is certainly nothing more to read
        next_poll = None
    else:
        # Task is not finished, so there might be more to read,
        # check back soon
        next_poll = LOG_WATCHER_INTERVAL

    if six.PY3:
        content = str(content, encoding="utf-8", errors="replace")

    result = {
        "new_offset": offset + len(content),
        "task_finished": task.is_finished() and 1 or 0,
        "next_poll": next_poll,
        "content": content,
    }

    return HttpResponse(json.dumps(result), content_type="application/json")


class LoginView(django.contrib.auth.views.LoginView):
    extra_context = {'title': _('Login')}
    template_name = 'auth/login.html'

class LogoutView(django.contrib.auth.views.LogoutView):
    pass


def krb5login(request, redirect_field_name=REDIRECT_FIELD_NAME):
    #middleware = 'django.contrib.auth.middleware.RemoteUserMiddleware'
    middleware = 'kobo.django.auth.middleware.LimitedRemoteUserMiddleware'
    if middleware not in settings.MIDDLEWARE:
        raise ImproperlyConfigured("krb5login view requires '%s' middleware installed" % middleware)
    redirect_to = request.POST.get(redirect_field_name, "")
    if not redirect_to:
        redirect_to = request.GET.get(redirect_field_name, "")
    if not redirect_to:
        redirect_to = reverse("home/index")
    return RedirectView.as_view(url=redirect_to, permanent=True)(request)

def oidclogin(request):
    middleware = 'kobo.django.auth.middleware.LimitedRemoteUserMiddleware'
    if middleware not in settings.MIDDLEWARE:
        raise ImproperlyConfigured("oidclogin view requires '%s' middleware installed" % middleware)
    return RedirectView.as_view(url=reverse("home/index"), permanent=False)(request)
