#!/usr/bin/python3
# -*- coding: utf-8 -*-
#
# Copyright © 2013-2014 Andrea Colangelo <warp10@debian.org>
# Copyright © 2014-2017 Mattia Rizzolo <mattia@debian.org>
#
# This work is free. You can redistribute it and/or modify it under the
# terms of the Do What The Fuck You Want To Public License, Version 2,
# as published by Sam Hocevar. The full text of the license is available at:
# http://www.wtfpl.net/txt/copying/


import os
import sys
import attr
import yaml
import datetime
import psycopg2

from jinja2 import Template


WEBDIR = os.path.dirname(__file__)
REPORT = "{}/report.html".format(WEBDIR)
COMMENTS_FILE = "{}/comments.yml".format(WEBDIR)

TIMESTAMP = datetime.datetime.utcnow().strftime("%A, %d %B %Y, %H:%M UTC")


@attr.s
class Bug(object):
    bug = attr.ib()
    title = attr.ib()
    open_date = attr.ib()
    status = attr.ib()
    severity = attr.ib()
    affects_testing = attr.ib()
    tags = attr.ib()
    usertags = attr.ib()


@attr.s
class Package(object):
    name = attr.ib()
    bugs = None
    level = None
    colour = None

    def __str__(self):
        return "Package({} — Bugs: <{}>".format(self.name, self.bugs)

    def add_bug(self, bug: Bug):
        try:
            self.bugs.append(bug)
        except AttributeError:
            self.bugs = [bug]

    def finalize(self):
        # FIXME: the behaviour of this thing is pretty random: it will set the
        # colour/severity only according to the last bug in the package, which
        # are ordered randomly
        def check_patch(self, bug):
            if "pending" in bug.tags:
                self.level += 3
            elif "patch" in bug.tags:
                self.level -= 3

        def check_comment(self, bug):
            if Comment(bug.bug).get().startswith("RM"):
                self.level += 100
            elif Comment(bug.bug).get().startswith("NO"):
                self.level += 50
            elif Comment(bug.bug).get():
                self.level += 4

        for bug in self.bugs:
            if "ftbfs" in bug.usertags:
                self.level = 45
                self.colour = "ftbfs"
                check_patch(self, bug)
                check_comment(self, bug)
                return
            elif bug.severity in ("critical", "grave", "serious"):
                self.level = 5
                self.colour = "rc"
                check_patch(self, bug)
                check_comment(self, bug)
                return
            elif bug.severity == "important":
                self.level = 15
                self.colour = "important"
                check_patch(self, bug)
                check_comment(self, bug)
                return
            elif bug.severity in ("normal", "minor"):
                self.level = 25
                self.colour = "minor"
                check_patch(self, bug)
                check_comment(self, bug)
                return
            else:
                self.colour = "wishlist"
                self.level = 35
                check_patch(self, bug)
                check_comment(self, bug)
                return

    def html_row(self):
        template = Template(
            """
<tr class="{{ pkg.colour }}">
  <td rowspan="{{ pkg.bugs|length }}">
    <b>{{ pkg.name }}</b><br />
        <a href="https://tracker.debian.org/{{ pkg.name }}" target="_blank">PTS</a>
        <a href="https://bugs.debian.org/src:{{ pkg.name }}" target="_blank">BTS</a>
        <a href="https://buildd.debian.org/{{ pkg.name }}" target="_blank">Buildd</a>
        <a href="https://tests.reproducible-builds.org/debian/{{ pkg.name }}" target="_blank">r-b</a>
        <br /><span class="small">{{ pkg.maint }}</span>
  </td>
  {% for bug in pkg.bugs %}
    {% if not loop.first %}<tr class="{{ pkg.colour }}">{% endif %}
    <td><b>{{ bug.severity|trim|truncate(1, true, '')|upper }}</b>
    <td class="nobr"><nobr>
        <a href="https://bugs.debian.org/{{ bug.bug }}">#{{ bug.bug }}</a>
        {% if 'pending' in bug.tags %}
            <span class="bug-pending"">P</span>
        {% elif 'patch' in bug.tags %}
            <span class="bug-patch"">+</span>
        {% endif %}
    </nobr></td>
    <td>{{ bug.tags|reject('equalto', 'pending')|reject('equalto', 'patch')|join (', ') }}</td>
    <td>{{ bug.usertags|join(', ') }}</td>
    <td {% if not bug.affects_testing %} class="notesting" {% endif %}>
        {{ bug.title }}
    </td>
    <td>{{ get_comment(bug.bug)|urlize }}</td>
    </tr>
  {% endfor %}
"""
        )
        template.globals["get_comment"] = lambda x: Comment(x).get()

        return template.render(pkg=self)


@attr.s
class Page(object):
    path = attr.ib()

    _header = Template(
        """<!DOCTYPE html>
<html>
<!--
Copyright © 2013-2014 Andrea Colangelo <warp10@debian.org>
            2014-2017 Mattia Rizzolo <mattia@debian.org>
Released under the terms of the WTFPL http://www.wtfpl.net/
-->
<head>
        <meta http-equiv="Content-Type" content="text/html;charset=utf-8" >
        <title>Reproducible Bugs report</title>
        <link href="style.css" rel="stylesheet" />
</head>
<body>
        <p> Report generated on: <b>{{ time }}</b><br>"""
    )

    _header = _header.render(time=TIMESTAMP)
    _footer = "</body></html>"

    def write(self, text, mode):
        with open(self.path, mode) as f:
            f.write(text)

    def write_header(self):
        self.write(self._header, "w+")

    def write_footer(self):
        self.write(self._footer, "a")


class Comments(object):
    _singleton = {}

    def __init__(self):
        self.__dict__ = self._singleton

    def get(self):
        try:
            return self._comments
        except AttributeError:
            try:
                with open(COMMENTS_FILE, "r") as f:
                    self._comments = yaml.safe_load(f)
                    if not self._comments:
                        self._comments = {}
            except FileNotFoundError:
                self._comments = {}
            return self._comments

    def clean(self, conn):
        self.get()
        bugs = [str(x) for x in self._comments.keys()]
        query = "SELECT id FROM bugs WHERE id IN ({}) AND done != ''".format(
            ", ".join(bugs)
        )
        closed_bugs = query_udd(conn, query)
        for bug in closed_bugs:
            print(
                "Bug https://bugs.debian.org/{} has been closed".format(bug[0])
            )


@attr.s
class Comment(object):
    key = attr.ib()

    def get(self):
        try:
            return self._comment
        except AttributeError:
            all_comments = Comments().get()
            try:
                self._comment = all_comments[self.key]
            except KeyError:
                self._comment = ""
            return self._comment


def write_table(data: list):
    REPORT.write("<table>", "a")
    REPORT.write(
        "<tr><th>package</th><th>sev</th><th>#</th><th>tags</th>"
        "<th>usertags</th><th>title</th><th>comments</th>",
        "a",
    )
    for pkg in sorted(data, key=lambda x: (x.level, x.name)):
        REPORT.write(pkg.html_row(), "a")
    REPORT.write("</table>", "a")


def query_udd(conn, query):
    """Actually execute a query on udd"""
    cursor = conn.cursor()
    cursor.execute(query)
    return cursor.fetchall()


def get_data(conn):
    packages = []
    query = """
        SELECT DISTINCT
            b.source, s.maintainer_email,
            b.id, b.title, b.arrival, b.status, b.severity,
            b.affects_testing,
            ARRAY_AGG(DISTINCT t.tag), ARRAY_AGG(DISTINCT u.tag)
        FROM
            bugs AS b
            JOIN bugs_usertags AS u ON b.id=u.id
            JOIN (
                SELECT source, maintainer_email, version
                FROM sources
                WHERE distribution='debian' AND release='sid'
                ORDER BY version DESC
            ) AS s on s.source=b.source
            LEFT JOIN (
                SELECT id, tag FROM bugs_tags
                WHERE tag IN ('patch', 'pending')
            ) AS t ON b.id=t.id
        WHERE
            u.email='reproducible-builds@lists.alioth.debian.org'
            AND b.done = ''
            AND b.id NOT IN (SELECT id FROM bugs_fixed_in)
        GROUP BY b.source, s.maintainer_email, b.id
    """
    keys = (
        "source",
        "maint",
        "bug",
        "title",
        "open_date",
        "status",
        "severity",
        "affects_testing",
        "tags",
        "usertags",
    )
    tmp_pkgs = {}
    for row in query_udd(conn, query):
        item = dict(zip(keys, row))
        src = item.pop("source")
        maint = item.pop("maint")
        bug = Bug(**item)
        try:
            tmp_pkgs[src].add_bug(bug)
        except KeyError:
            tmp_pkgs[src] = Package(src)
            tmp_pkgs[src].maint = maint
            tmp_pkgs[src].add_bug(bug)
    packages = list(tmp_pkgs.values())
    for pkg in packages:
        pkg.finalize()
    return packages


if __name__ == "__main__":
    REPORT = Page(REPORT)

    try:
        try:
            conn = psycopg2.connect("service=udd")
        except Exception:
            conn = psycopg2.connect(
                host="udd-mirror.debian.net",
                user="udd-mirror",
                password="udd-mirror",
                database="udd",
            )
    except Exception:
        print("Can't connect to UDD", file=sys.stderr)
        raise
        sys.exit(1)
    else:
        conn.set_client_encoding("UTF8")

    if "-c" in sys.argv or "--clean" in sys.argv:
        Comments().clean(conn)
        sys.exit(0)

    data = get_data(conn)
    REPORT.write_header()
    write_table(data)
    REPORT.write_footer()

    Comments().clean(conn)
