#!/usr/bin/env python3
#
# Copyright 2016 Red Hat, Inc.
#
# Authors:
#     Fam Zheng <famz@redhat.com>
#
# This work is licensed under the MIT License.  Please see the LICENSE file or
# http://opensource.org/licenses/MIT.
import urllib

from django.shortcuts import render
from django.http import HttpResponse, Http404
from django.db.models import Exists, OuterRef
from django.urls import reverse
from django.utils.html import format_html
from django.conf import settings
import api
from mod import dispatch_module_hook
import subprocess

PAGE_SIZE = 50


def try_get_git_head():
    try:
        return (
            "-"
            + subprocess.check_output(["git", "rev-parse", "--short", "HEAD"]).decode()
        )
    except Exception:
        return ""


def render_page(request, template_name, **data):
    data["patchew_version"] = settings.VERSION + try_get_git_head()
    dispatch_module_hook("render_page_hook", request=request, context_data=data)
    return render(request, template_name, context=data)


def prepare_message(request, project, m, detailed):
    name, addr = m.sender
    m.sender_full_name = "%s <%s>" % (name, addr)
    m.sender_display_name = name or addr
    m.age = m.get_age()
    m.url = reverse(
        "series_detail", kwargs={"project": project.name, "message_id": m.message_id}
    )
    m.status_tags = []
    m.extra_links = []
    if m.is_series_head:
        m.num_patches = m.get_num_patches()
        m.total_patches = m.get_total_patches()
        if m.num_patches < m.total_patches:
            missing = m.total_patches - m.num_patches
            m.status_tags.append(
                {
                    "title": "Series not complete (%d %s not received)"
                    % (missing, "patches" if missing > 1 else "patch"),
                    "type": "warning",
                    "char": "?",
                }
            )

    # hook points for plugins
    m.has_other_revisions = False
    m.extra_status = []
    m.extra_ops = []
    dispatch_module_hook(
        "prepare_message_hook", request=request, message=m, detailed=detailed
    )
    if m.is_merged:
        m.status_tags = [
            {"title": "Series merged", "type": "success", "char": "Merged"}
        ]
    return m


def prepare_patches(request, m, max_depth=None):
    if m.total_patches == 1:
        return []
    replies = m.get_replies().filter(is_patch=True)
    commit_replies = api.models.Message.objects.filter(
        in_reply_to=OuterRef("message_id")
    )
    replies = replies.annotate(has_replies=Exists(commit_replies))
    project = m.project
    return [prepare_message(request, project, x, True) for x in replies]


def prepare_series(request, s, skip_patches=False):
    r = []
    project = s.project

    def add_msg_recurse(m, skip_patches, depth=0):
        a = prepare_message(request, project, m, True)
        a.indent_level = min(depth, 4)
        r.append(a)
        replies = m.get_replies()
        non_patches = [x for x in replies if not x.is_patch]
        patches = []
        if not skip_patches:
            patches = [x for x in replies if x.is_patch]
        for x in non_patches + patches:
            add_msg_recurse(x, False, depth + 1)

    add_msg_recurse(s, skip_patches)
    return r


def prepare_results(request, obj):
    rendered_results = []
    for result in obj.results.all():
        html = result.render()
        if html is None:
            continue
        result.html = html
        rendered_results.append(result)
    return rendered_results


def prepare_series_list(request, sl):
    return [prepare_message(request, s.project, s, False) for s in sl]


def prepare_projects():
    return api.models.Project.objects.filter(parent_project=None).order_by(
        "-display_order", "name"
    )


def view_project_list(request):
    return render_page(request, "project-list.html", projects=prepare_projects())


def gen_page_links(total, cur_page, pagesize, extra_params):
    max_page = int((total + pagesize - 1) / pagesize)
    ret = []
    ddd = False
    for i in range(1, max_page + 1):
        if i == cur_page:
            ret.append(
                {
                    "title": str(i),
                    "url": "?page=" + str(i) + extra_params,
                    "class": "active",
                }
            )
            ddd = False
        elif i < 10 or abs(i - cur_page) < 3 or max_page - i < 3:
            ret.append({"title": str(i), "url": "?page=" + str(i) + extra_params})
            ddd = False
        else:
            if not ddd:
                ret.append({"title": "...", "class": "disabled", "url": "#"})
                ddd = True

    return ret


def get_page_from_request(request):
    try:
        return int(request.GET["page"])
    except Exception:
        return 1


def prepare_navigate_list(cur, *path):
    """ each path is (view_name, kwargs, title) """
    r = [{"url": reverse("project_list"), "title": "Patchew"}]
    for it in path:
        r.append({"url": reverse(it[0], kwargs=it[1]), "title": it[2]})
    r.append({"title": cur, "url": "", "class": "active"})
    return r


def render_series_list_page(request, query, search=None, project=None, keywords=[]):
    sort = request.GET.get("sort")
    if sort == "replied":
        sortfield = "-last_reply_date"
        order_by_reply = True
    else:
        sortfield = "-date"
        order_by_reply = False
    if sortfield:
        query = query.order_by(sortfield)
    query = query.prefetch_related("topic")
    cur_page = get_page_from_request(request)
    start = (cur_page - 1) * PAGE_SIZE
    series = query[start : start + PAGE_SIZE]
    if not series and cur_page > 1:
        raise Http404("Page not found")
    params = ""
    if sort:
        params += "&" + urllib.parse.urlencode({"sort": sort})
    if search is not None:
        is_search = True
        params += "&" + urllib.parse.urlencode({"q": search})
        cur = 'search "%s"' % search
        if project:
            nav_path = prepare_navigate_list(
                cur, ("series_list", {"project": project}, project)
            )
        else:
            nav_path = prepare_navigate_list(cur)
    else:
        is_search = False
        search = "project:%s" % project
        nav_path = prepare_navigate_list(project)
    page_links = gen_page_links(query.count(), cur_page, PAGE_SIZE, params)
    return render_page(
        request,
        "series-list.html",
        series=prepare_series_list(request, series),
        page_links=page_links,
        search=search,
        project=project,
        is_search=is_search,
        keywords=keywords,
        order_by_reply=order_by_reply,
        navigate_links=nav_path,
    )


def view_search_help(request):
    from markdown import markdown

    nav_path = prepare_navigate_list("Search help")
    return render_page(
        request,
        "search-help.html",
        navigate_links=nav_path,
        search_help_doc=markdown(api.search.SearchEngine.__doc__),
    )


def view_project_detail(request, project):
    po = api.models.Project.objects.filter(name=project).first()
    if not po:
        raise Http404("Project not found")
    nav_path = prepare_navigate_list(
        "Information", ("series_list", {"project": project}, project)
    )
    po.extra_info = []
    po.extra_status = []
    po.extra_ops = []
    dispatch_module_hook("prepare_project_hook", request=request, project=po)
    return render_page(
        request,
        "project-detail.html",
        results=prepare_results(request, po),
        project=po,
        navigate_links=nav_path,
        search="",
    )


def view_search(request):
    from api.search import SearchEngine

    search = request.GET.get("q", "").strip()
    terms = [x.strip() for x in search.split(" ") if x]
    se = SearchEngine()
    query = se.search_series(user=request.user, *terms)
    return render_series_list_page(
        request, query, search=search, project=se.project(), keywords=se.last_keywords()
    )


def view_series_list(request, project):
    prj = api.models.Project.objects.filter(name=project).first()
    if not prj:
        raise Http404("Project not found")
    query = api.models.Message.objects.series_heads(prj.id)
    return render_series_list_page(request, query, project=project)


def view_mbox(request, project, message_id):
    s = api.models.Message.objects.find_message(message_id, project)
    if not s:
        raise Http404("Series not found")
    mbox = s.get_mbox_with_tags()
    if not mbox:
        raise Http404("Series not complete")
    return HttpResponse(mbox, content_type="text/plain")


def view_series_detail(request, project, message_id):
    s = api.models.Message.objects.find_series(message_id, project)
    if not s:
        raise Http404("Series not found")
    nav_path = prepare_navigate_list(
        "View series", ("series_list", {"project": project}, project)
    )
    search = "id:" + message_id
    is_cover_letter = not s.is_patch
    messages = prepare_series(request, s, is_cover_letter)
    series = messages[0]
    if s.num_patches >= s.total_patches:
        mbox_url = reverse(
            "mbox", kwargs={"project": project, "message_id": message_id}
        )
        title = "Download series mbox" if is_cover_letter else "Download mbox"
        series.extra_links.append(
            {
                "html": format_html('<a href="{}">{}</a>', mbox_url, title),
                "icon": "download",
            }
        )
    return render_page(
        request,
        "series-detail.html",
        subject=s.subject,
        stripped_subject=s.stripped_subject,
        has_other_revisions=series.has_other_revisions,
        version=s.version,
        message_id=s.message_id,
        series=series,
        is_cover_letter=is_cover_letter,
        is_head=True,
        project=project,
        navigate_links=nav_path,
        search=search,
        results=prepare_results(request, s),
        patches=prepare_patches(request, s),
        messages=messages,
    )


def view_series_message(request, project, thread_id, message_id):
    s = api.models.Message.objects.find_series(thread_id, project)
    if not s:
        raise Http404("Series not found")
    m = api.models.Message.objects.filter(
        message_id=message_id, in_reply_to=thread_id
    ).first()
    if not m:
        raise Http404("Message not found")
    nav_path = prepare_navigate_list(
        "View patch",
        ("series_list", {"project": project}, project),
        ("series_detail", {"project": project, "message_id": thread_id}, s.subject),
    )
    search = "id:" + thread_id
    series = prepare_message(request, s.project, s, True)
    messages = prepare_series(request, m)
    mbox_url = reverse("mbox", kwargs={"project": project, "message_id": message_id})
    series.extra_links.append(
        {
            "html": format_html('<a href="{}">Download mbox</a>', mbox_url),
            "icon": "download",
        }
    )
    return render_page(
        request,
        "series-detail.html",
        subject=m.subject,
        stripped_subject=s.stripped_subject,
        has_other_revisions=series.has_other_revisions,
        version=m.version,
        message_id=m.message_id,
        series=series,
        is_cover_letter=False,
        is_head=False,
        project=project,
        navigate_links=nav_path,
        search=search,
        results=[],
        patches=prepare_patches(request, s),
        messages=messages,
    )