#!/usr/bin/python3
# -*- coding: utf-8 -*-
#
# (C) 2007 Canonical Ltd., Steve Kowalik
# Authors:
#  Martin Pitt <martin.pitt@ubuntu.com>
#  Steve Kowalik <stevenk@ubuntu.com>
#  Michael Bienia <geser@ubuntu.com>
#  Daniel Hahler <ubuntu@thequod.de>
#  Iain Lane <laney@ubuntu.com>
#  Jonathan Davies <jpds@ubuntu.com>
#  Markus Korn <thekorn@gmx.de> (python-launchpadlib support)
#
# ##################################################################
#
# 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; version 2.
#
# 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-2 for more details.
#
# ##################################################################

import argparse
import os
import sys

from distro_info import UbuntuDistroInfo

from ubuntutools import getLogger
from ubuntutools.config import UDTConfig, ubu_email
from ubuntutools.lp import udtexceptions
from ubuntutools.misc import require_utf8
from ubuntutools.question import EditBugReport, confirmation_prompt
from ubuntutools.version import Version

Logger = getLogger()

#
# entry point
#


def main():
    # Our usage options.
    usage = "%(prog)s [options] <source package> [<target release> [base version]]"
    parser = argparse.ArgumentParser(usage=usage)

    parser.add_argument(
        "-d", dest="dist", default="unstable", help="Debian distribution to sync from."
    )
    parser.add_argument(
        "-k",
        dest="keyid",
        default=None,
        help="GnuPG key ID to use for signing report "
        "(only used when emailing the sync request).",
    )
    parser.add_argument(
        "-n",
        action="store_true",
        dest="newpkg",
        default=False,
        help="Whether package to sync is a new package in Ubuntu.",
    )
    parser.add_argument(
        "--email",
        action="store_true",
        default=False,
        help="Use a PGP-signed email for filing the sync request, rather than the LP API.",
    )
    parser.add_argument(
        "--lp",
        dest="deprecated_lp_flag",
        action="store_true",
        default=False,
        help=argparse.SUPPRESS,
    )
    parser.add_argument(
        "-l",
        "--lpinstance",
        metavar="INSTANCE",
        dest="lpinstance",
        default=None,
        help="Launchpad instance to connect to (default: production).",
    )
    parser.add_argument(
        "-s", action="store_true", dest="sponsorship", default=False, help="Force sponsorship"
    )
    parser.add_argument(
        "-C",
        action="store_true",
        dest="missing_changelog_ok",
        default=False,
        help="Allow changelog to be manually filled in when missing",
    )
    parser.add_argument(
        "-e",
        action="store_true",
        dest="ffe",
        default=False,
        help="Use this after FeatureFreeze for non-bug fix "
        "syncs, changes default subscription to the "
        "appropriate release team.",
    )
    parser.add_argument(
        "--no-conf",
        action="store_true",
        dest="no_conf",
        default=False,
        help="Don't read config files or environment variables",
    )
    parser.add_argument("source_package", help=argparse.SUPPRESS)
    parser.add_argument("release", nargs="?", help=argparse.SUPPRESS)
    parser.add_argument("base_version", nargs="?", type=Version, help=argparse.SUPPRESS)
    args = parser.parse_args()

    require_utf8()

    config = UDTConfig(args.no_conf)

    if args.deprecated_lp_flag:
        Logger.info("The --lp flag is now default, ignored.")
    if args.email:
        args.lpapi = False
    else:
        args.lpapi = config.get_value("USE_LPAPI", default=True, boolean=True)
    if args.lpinstance is None:
        args.lpinstance = config.get_value("LPINSTANCE")

    if args.keyid is None:
        args.keyid = config.get_value("KEYID")

    if not args.lpapi:
        if args.lpinstance == "production":
            bug_mail_domain = "bugs.launchpad.net"
        elif args.lpinstance == "staging":
            bug_mail_domain = "bugs.staging.launchpad.net"
        else:
            Logger.error("Error: Unknown launchpad instance: %s", args.lpinstance)
            sys.exit(1)

    mailserver_host = config.get_value(
        "SMTP_SERVER", default=None, compat_keys=["UBUSMTP", "DEBSMTP"]
    )
    if not args.lpapi and not mailserver_host:
        try:
            import DNS  # pylint: disable=import-outside-toplevel

            DNS.DiscoverNameServers()
            mxlist = DNS.mxlookup(bug_mail_domain)
            firstmx = mxlist[0]
            mailserver_host = firstmx[1]
        except ImportError:
            Logger.error("Please install python-dns to support Launchpad mail server lookup.")
            sys.exit(1)

    mailserver_port = config.get_value(
        "SMTP_PORT", default=25, compat_keys=["UBUSMTP_PORT", "DEBSMTP_PORT"]
    )
    mailserver_user = config.get_value("SMTP_USER", compat_keys=["UBUSMTP_USER", "DEBSMTP_USER"])
    mailserver_pass = config.get_value("SMTP_PASS", compat_keys=["UBUSMTP_PASS", "DEBSMTP_PASS"])

    # import the needed requestsync module
    # pylint: disable=import-outside-toplevel
    if args.lpapi:
        from ubuntutools.lp.lpapicache import Distribution, Launchpad
        from ubuntutools.requestsync.lp import (
            check_existing_reports,
            get_debian_srcpkg,
            get_ubuntu_delta_changelog,
            get_ubuntu_srcpkg,
            need_sponsorship,
            post_bug,
        )

        # See if we have LP credentials and exit if we don't -
        # cannot continue in this case

        try:
            # devel for changelogUrl()
            Launchpad.login(service=args.lpinstance, api_version="devel")
        except IOError:
            sys.exit(1)
    else:
        from ubuntutools.requestsync.mail import (
            check_existing_reports,
            get_debian_srcpkg,
            get_ubuntu_delta_changelog,
            get_ubuntu_srcpkg,
            mail_bug,
            need_sponsorship,
        )

        if not any(x in os.environ for x in ("UBUMAIL", "DEBEMAIL", "EMAIL")):
            Logger.error(
                "The environment variable UBUMAIL, DEBEMAIL or EMAIL needs "
                "to be set to let this script mail the sync request."
            )
            sys.exit(1)

    newsource = args.newpkg
    sponsorship = args.sponsorship
    distro = args.dist
    ffe = args.ffe
    lpapi = args.lpapi
    need_interaction = False
    srcpkg = args.source_package

    if not args.release:
        if lpapi:
            args.release = Distribution("ubuntu").getDevelopmentSeries().name
        else:
            ubu_info = UbuntuDistroInfo()
            args.release = ubu_info.devel()
        Logger.warning("Target release missing - assuming %s", args.release)

    # Get the current Ubuntu source package
    try:
        ubuntu_srcpkg = get_ubuntu_srcpkg(srcpkg, args.release, "Proposed")
        ubuntu_version = Version(ubuntu_srcpkg.getVersion())
        ubuntu_component = ubuntu_srcpkg.getComponent()
        newsource = False  # override the -n flag
    except udtexceptions.PackageNotFoundException:
        ubuntu_srcpkg = None
        ubuntu_version = Version("~")
        ubuntu_component = None  # Set after getting the Debian info
        if not newsource:
            Logger.info("'%s' doesn't exist in 'Ubuntu %s'.", srcpkg, args.release)
            Logger.info("Do you want to sync a new package?")
            confirmation_prompt()
            newsource = True
    except udtexceptions.SeriesNotFoundException as error:
        Logger.error(error)
        sys.exit(1)

    # Get the requested Debian source package
    try:
        debian_srcpkg = get_debian_srcpkg(srcpkg, distro)
        debian_version = Version(debian_srcpkg.getVersion())
        debian_component = debian_srcpkg.getComponent()
    except udtexceptions.PackageNotFoundException as error:
        Logger.error(error)
        sys.exit(1)
    except udtexceptions.SeriesNotFoundException as error:
        Logger.error(error)
        sys.exit(1)

    if ubuntu_component is None:
        if debian_component == "main":
            ubuntu_component = "universe"
        else:
            ubuntu_component = "multiverse"

    # Stop if Ubuntu has already the version from Debian or a newer version
    if (ubuntu_version >= debian_version) and args.lpapi:
        # try rmadison
        import ubuntutools.requestsync.mail  # pylint: disable=import-outside-toplevel

        try:
            debian_srcpkg = ubuntutools.requestsync.mail.get_debian_srcpkg(srcpkg, distro)
            debian_version = Version(debian_srcpkg.getVersion())
            debian_component = debian_srcpkg.getComponent()
        except udtexceptions.PackageNotFoundException as error:
            Logger.error(error)
            sys.exit(1)

    if ubuntu_version == debian_version:
        Logger.error(
            "The versions in Debian and Ubuntu are the same already (%s). Aborting.",
            ubuntu_version,
        )
        sys.exit(1)
    if ubuntu_version > debian_version:
        Logger.error(
            "The version in Ubuntu (%s) is newer than the version in Debian (%s). Aborting.",
            ubuntu_version,
            debian_version,
        )
        sys.exit(1)

    # -s flag not specified - check if we do need sponsorship
    if not sponsorship:
        sponsorship = need_sponsorship(srcpkg, ubuntu_component, args.release)

    if not sponsorship and not ffe:
        Logger.error(
            "Consider using syncpackage(1) for syncs that "
            "do not require feature freeze exceptions."
        )

    # Check for existing package reports
    if not newsource:
        check_existing_reports(srcpkg)

    # Generate bug report
    pkg_to_sync = (
        f"{srcpkg} {debian_version} ({ubuntu_component})"
        f" from Debian {distro} ({debian_component})"
    )
    title = f"Sync {pkg_to_sync}"
    if ffe:
        title = "FFe: " + title
    report = f"Please sync {pkg_to_sync}\n\n"

    if "ubuntu" in str(ubuntu_version):
        need_interaction = True

        Logger.info("Changes have been made to the package in Ubuntu.")
        Logger.info("Please edit the report and give an explanation.")
        Logger.info("Not saving the report file will abort the request.")
        report += (
            f"Explanation of the Ubuntu delta and why it can be dropped:\n"
            f"{get_ubuntu_delta_changelog(ubuntu_srcpkg)}\n>>> ENTER_EXPLANATION_HERE <<<\n\n"
        )

    if ffe:
        need_interaction = True

        Logger.info("To approve FeatureFreeze exception, you need to state")
        Logger.info("the reason why you feel it is necessary.")
        Logger.info("Not saving the report file will abort the request.")
        report += "Explanation of FeatureFreeze exception:\n>>> ENTER_EXPLANATION_HERE <<<\n\n"

    if need_interaction:
        confirmation_prompt()

    base_version = args.base_version or ubuntu_version

    if newsource:
        report += "All changelog entries:\n\n"
    else:
        report += f"Changelog entries since current {args.release} version {ubuntu_version}:\n\n"
    changelog = debian_srcpkg.getChangelog(since_version=base_version)
    if not changelog:
        if not args.missing_changelog_ok:
            Logger.error(
                "Did not retrieve any changelog entries. "
                "Do you need to specify '-C'? "
                "Was the package recently uploaded? (check "
                "http://packages.debian.org/changelogs/)"
            )
            sys.exit(1)
        else:
            need_interaction = True
            changelog = "XXX FIXME: add changelog here XXX"
    report += changelog

    editor = EditBugReport(title, report)
    editor.edit(optional=not need_interaction)
    title, report = editor.get_report()

    if "XXX FIXME" in report:
        Logger.error(
            "changelog boilerplate found in report, "
            "please manually add changelog when using '-C'"
        )
        sys.exit(1)

    # bug status and bug subscriber
    status = "confirmed"
    subscribe = "ubuntu-archive"
    if sponsorship:
        status = "new"
        subscribe = "ubuntu-sponsors"
    if ffe:
        status = "new"
        subscribe = "ubuntu-release"

    srcpkg = None if newsource else srcpkg
    if lpapi:
        # Map status to the values expected by LP API
        mapping = {"new": "New", "confirmed": "Confirmed"}
        # Post sync request using LP API
        post_bug(srcpkg, subscribe, mapping[status], title, report)
    else:
        email_from = ubu_email(export=False)[1]
        # Mail sync request
        mail_bug(
            srcpkg,
            subscribe,
            status,
            title,
            report,
            bug_mail_domain,
            args.keyid,
            email_from,
            mailserver_host,
            mailserver_port,
            mailserver_user,
            mailserver_pass,
        )


if __name__ == "__main__":
    try:
        main()
    except KeyboardInterrupt:
        Logger.error("User abort.")
        sys.exit(2)