#!/usr/bin/python3
# -*- coding: utf-8 -*-
#
# submittodebian - tool to submit patches to Debian's BTS
# Copyright (C) 2007, 2009 Canonical Ltd.
# Author: Soren Hansen <soren@ubuntu.com>,
#         Steve Langasek <slangasek@canonical.com>
#
# ##################################################################
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# See file /usr/share/common-licenses/GPL for more details.
#
# ##################################################################

"""Submit the Ubuntu changes in a package to Debian.

Run inside an unpacked Ubuntu source package.
"""

import argparse
import os
import re
import shutil
import sys
from subprocess import DEVNULL, PIPE, Popen, call, check_call, run
from tempfile import mkdtemp

from debian.changelog import Changelog
from distro_info import DistroDataOutdated, UbuntuDistroInfo

from ubuntutools import getLogger
from ubuntutools.config import ubu_email
from ubuntutools.question import EditFile, YesNoQuestion
from ubuntutools.update_maintainer import restore_maintainer, update_maintainer

Logger = getLogger()


def get_most_recent_debian_version(changelog):
    for block in changelog:
        version = block.version.full_version
        if not re.search("(ubuntu|build)", version):
            return version
    return None


def get_bug_body(changelog):
    entry = next(iter(changelog))
    msg = """
In Ubuntu, the attached patch was applied to achieve the following:

## ---------------- REPLACE THIS WITH ACTUAL INFORMATION ---------------------
## Please add all necessary information about why the change needed to go in
## Ubuntu, quote policy, spec or any other background material and why it can
## and should be used in Debian too.  If the patch is composed of multiple
## independent pieces, please send them as separate bug reports.
## ---------------- REPLACE THIS WITH ACTUAL INFORMATION ---------------------

%s

Thanks for considering the patch.
""" % (
        "\n".join(entry.changes())
    )
    return msg


def build_source_package():
    if os.path.isdir(".bzr"):
        cmd = ["bzr", "bd", "--builder=dpkg-buildpackage", "-S", "--", "-uc", "-us", "-nc"]
    else:
        cmd = ["dpkg-buildpackage", "-S", "-uc", "-us", "-nc"]
    env = os.environ.copy()
    # Unset DEBEMAIL in case there's an @ubuntu.com e-mail address
    env.pop("DEBEMAIL", None)
    check_call(cmd, env=env)


def gen_debdiff(tmpdir, changelog):
    pkg = changelog.package

    changelog_it = iter(changelog)
    newver = next(changelog_it).version
    oldver = next(changelog_it).version

    debdiff = os.path.join(tmpdir, f"{pkg}_{newver}.debdiff")

    diff_cmd = ["bzr", "diff", "-r", "tag:" + str(oldver)]
    if call(diff_cmd, stdout=DEVNULL, stderr=DEVNULL) == 1:
        Logger.info("Extracting bzr diff between %s and %s", oldver, newver)
    else:
        if oldver.epoch is not None:
            oldver = str(oldver)[str(oldver).index(":") + 1 :]
        if newver.epoch is not None:
            newver = str(newver)[str(newver).index(":") + 1 :]

        olddsc = f"../{pkg}_{oldver}.dsc"
        newdsc = f"../{pkg}_{newver}.dsc"

        check_file(olddsc)
        check_file(newdsc)

        Logger.info("Generating debdiff between %s and %s", oldver, newver)
        diff_cmd = ["debdiff", olddsc, newdsc]

    with Popen(diff_cmd, stdout=PIPE, encoding="utf-8") as diff:
        with open(debdiff, "w", encoding="utf-8") as debdiff_f:
            run(
                ["filterdiff", "-x", "*changelog*"],
                check=False,
                stdin=diff.stdout,
                stdout=debdiff_f,
                encoding="utf-8",
            )

    return debdiff


def check_file(fname, critical=True):
    if os.path.exists(fname):
        return fname
    if not critical:
        return False
    Logger.info("Couldn't find «%s».\n", fname)
    sys.exit(1)


def submit_bugreport(body, debdiff, deb_version, changelog):
    try:
        devel = UbuntuDistroInfo().devel()
    except DistroDataOutdated as e:
        Logger.info(str(e))
        devel = ""

    if os.path.dirname(sys.argv[0]).startswith("/usr/bin"):
        editor_path = "/usr/share/ubuntu-dev-tools"
    else:
        editor_path = os.path.dirname(sys.argv[0])
    env = dict(os.environ.items())
    if "EDITOR" in env:
        env["UDT_EDIT_WRAPPER_EDITOR"] = env["EDITOR"]
    if "VISUAL" in env:
        env["UDT_EDIT_WRAPPER_VISUAL"] = env["VISUAL"]
    env["EDITOR"] = os.path.join(editor_path, "enforced-editing-wrapper")
    env["VISUAL"] = os.path.join(editor_path, "enforced-editing-wrapper")
    env["UDT_EDIT_WRAPPER_TEMPLATE_RE"] = ".*REPLACE THIS WITH ACTUAL INFORMATION.*"
    env["UDT_EDIT_WRAPPER_FILE_DESCRIPTION"] = "bug report"

    # In external mua mode, attachments are lost (Reportbug bug: #679907)
    internal_mua = True
    for cfgfile in ("/etc/reportbug.conf", "~/.reportbugrc"):
        cfgfile = os.path.expanduser(cfgfile)
        if not os.path.exists(cfgfile):
            continue
        with open(cfgfile, "r", encoding="utf-8") as f:
            for line in f:
                line = line.strip()
                if line in ("gnus", "mutt", "nmh") or line.startswith("mua "):
                    internal_mua = False
                    break

    cmd = (
        "reportbug",
        "--no-check-available",
        "--no-check-installed",
        "--pseudo-header",
        "User: ubuntu-devel@lists.ubuntu.com",
        "--pseudo-header",
        f"Usertags: origin-ubuntu {devel} ubuntu-patch",
        "--tag",
        "patch",
        "--bts",
        "debian",
        "--include",
        body,
        "--attach" if internal_mua else "--include",
        debdiff,
        "--package-version",
        deb_version,
        changelog.package,
    )
    check_call(cmd, env=env)


def check_reportbug_config():
    reportbugrc_filename = os.path.expanduser("~/.reportbugrc")
    if os.path.exists(reportbugrc_filename):
        return
    email = ubu_email()[1]
    reportbugrc = f"""# Reportbug configuration generated by submittodebian(1)
# See reportbug.conf(5) for the configuration file format.

# Use Debian's reportbug SMTP Server:
# Note: it's limited to 5 connections per hour, and cannot CC you at submission
# time. See /usr/share/doc/reportbug/README.Users.gz for more details.
smtphost reportbug.debian.org:587
header "X-Debbugs-CC: {email}"
no-cc

# Use GMail's SMTP Server:
#smtphost smtp.googlemail.com:587
#smtpuser "<your address>@gmail.com"
#smtptls
"""

    with open(reportbugrc_filename, "w", encoding="utf-8") as f:
        f.write(reportbugrc)

    Logger.info(
        """\
You have not configured reportbug. Assuming this is the first time you have
used it. Writing a ~/.reportbugrc that will use Debian's mail server, and CC
the bug to you at <%s>

--- Generated ~/.reportbugrc ---
%s
--- End of ~/.reportbugrc ---

If this is not correct, please exit now and edit ~/.reportbugrc or run
reportbug --configure for its configuration wizard.
""",
        email,
        reportbugrc.strip(),
    )

    if YesNoQuestion().ask("Continue submitting this bug", "yes") == "no":
        sys.exit(1)


def main():
    parser = argparse.ArgumentParser(description=__doc__)
    parser.parse_args()

    if not os.path.exists("/usr/bin/reportbug"):
        Logger.error(
            "This utility requires the «reportbug» package, which isn't currently installed."
        )
        sys.exit(1)

    check_reportbug_config()
    changelog_file = check_file("debian/changelog", critical=False) or check_file(
        "../debian/changelog"
    )
    with open(changelog_file, encoding="utf-8") as f:
        changelog = Changelog(f.read())

    deb_version = get_most_recent_debian_version(changelog)
    bug_body = get_bug_body(changelog)

    tmpdir = mkdtemp()
    body = os.path.join(tmpdir, "bug_body")
    with open(body, "wb") as f:
        f.write(bug_body.encode("utf-8"))

    restore_maintainer("debian")
    build_source_package()
    update_maintainer("debian")

    debdiff = gen_debdiff(tmpdir, changelog)

    # Build again as the user probably doesn't expect the Maintainer to be
    # reverted in the most recent build
    build_source_package()

    EditFile(debdiff, "debdiff").edit(optional=True)

    submit_bugreport(body, debdiff, deb_version, changelog)
    os.unlink(body)
    os.unlink(debdiff)
    shutil.rmtree(tmpdir)


if __name__ == "__main__":
    main()
