#!/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 optparse
import os
import sys

from distro_info import UbuntuDistroInfo

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

#
# entry point
#


def main():
    # Our usage options.
    usage = ('Usage: %prog [options] '
             '<source package> [<target release> [base version]]')
    parser = optparse.OptionParser(usage)

    parser.add_option('-d', type='string',
                      dest='dist', default='unstable',
                      help='Debian distribution to sync from.')
    parser.add_option('-k', type='string',
                      dest='keyid', default=None,
                      help='GnuPG key ID to use for signing report '
                           '(only used when emailing the sync request).')
    parser.add_option('-n', action='store_true',
                      dest='newpkg', default=False,
                      help='Whether package to sync is a new package in '
                           'Ubuntu.')
    parser.add_option('--email', action='store_true', default=False,
                      help='Use a PGP-signed email for filing the sync '
                           'request, rather than the LP API.')
    parser.add_option('--lp', dest='deprecated_lp_flag',
                      action='store_true', default=False,
                      help=optparse.SUPPRESS_HELP)
    parser.add_option('-l', '--lpinstance', metavar='INSTANCE',
                      dest='lpinstance', default=None,
                      help='Launchpad instance to connect to '
                           '(default: production).')
    parser.add_option('-s', action='store_true',
                      dest='sponsorship', default=False,
                      help='Force sponsorship')
    parser.add_option('-C', action='store_true',
                      dest='missing_changelog_ok', default=False,
                      help='Allow changelog to be manually filled in '
                           'when missing')
    parser.add_option('-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_option('--no-conf', action='store_true',
                      dest='no_conf', default=False,
                      help="Don't read config files or environment variables")

    (options, args) = parser.parse_args()

    if not len(args):
        parser.print_help()
        sys.exit(1)

    require_utf8()

    config = UDTConfig(options.no_conf)

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

    if options.keyid is None:
        options.keyid = config.get_value('KEYID')

    if not options.lpapi:
        if options.lpinstance == 'production':
            bug_mail_domain = 'bugs.launchpad.net'
        elif options.lpinstance == 'staging':
            bug_mail_domain = 'bugs.staging.launchpad.net'
        else:
            print('Error: Unknown launchpad instance: %s' % options.lpinstance,
                  file=sys.stderr)
            sys.exit(1)

    mailserver_host = config.get_value('SMTP_SERVER',
                                       default=None,
                                       compat_keys=['UBUSMTP', 'DEBSMTP'])
    if not options.lpapi and not mailserver_host:
        try:
            import DNS
            DNS.DiscoverNameServers()
            mxlist = DNS.mxlookup(bug_mail_domain)
            firstmx = mxlist[0]
            mailserver_host = firstmx[1]
        except ImportError:
            print('Please install python3-dns to support Launchpad mail '
                  'server lookup.', file=sys.stderr)
            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
    if options.lpapi:
        from ubuntutools.requestsync.lp import (check_existing_reports,
                                                get_debian_srcpkg,
                                                get_ubuntu_srcpkg,
                                                get_ubuntu_delta_changelog,
                                                need_sponsorship, post_bug)
        from ubuntutools.lp.lpapicache import Distribution, Launchpad
        # See if we have LP credentials and exit if we don't -
        # cannot continue in this case

        try:
            # devel for changelogUrl()
            Launchpad.login(service=options.lpinstance, api_version='devel')
        except IOError:
            sys.exit(1)
    else:
        from ubuntutools.requestsync.mail import (check_existing_reports,
                                                  get_debian_srcpkg,
                                                  get_ubuntu_srcpkg,
                                                  get_ubuntu_delta_changelog,
                                                  mail_bug, need_sponsorship)
        if not any(x in os.environ for x in ('UBUMAIL', 'DEBEMAIL', 'EMAIL')):
            print('E: The environment variable UBUMAIL, DEBEMAIL or EMAIL '
                  'needs to be set to let this script mail the sync request.',
                  file=sys.stderr)
            sys.exit(1)

    newsource = options.newpkg
    sponsorship = options.sponsorship
    distro = options.dist
    ffe = options.ffe
    lpapi = options.lpapi
    need_interaction = False
    force_base_version = None
    srcpkg = args[0]

    if len(args) == 1:
        if lpapi:
            release = Distribution('ubuntu').getDevelopmentSeries().name
        else:
            ubu_info = UbuntuDistroInfo()
            release = ubu_info.devel()
        print('W: Target release missing - assuming %s' % release,
              file=sys.stderr)
    elif len(args) == 2:
        release = args[1]
    elif len(args) == 3:
        release = args[1]
        force_base_version = Version(args[2])
    else:
        print('E: Too many arguments.', file=sys.stderr)
        parser.print_help()
        sys.exit(1)

    # Get the current Ubuntu source package
    try:
        ubuntu_srcpkg = get_ubuntu_srcpkg(srcpkg, 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:
            print(("'%s' doesn't exist in 'Ubuntu %s'.\n"
                   "Do you want to sync a new package?")
                  % (srcpkg, release))
            confirmation_prompt()
            newsource = True
    except udtexceptions.SeriesNotFoundException as error:
        print("E: %s" % error, file=sys.stderr)
        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:
        print("E: %s" % error, file=sys.stderr)
        sys.exit(1)
    except udtexceptions.SeriesNotFoundException as error:
        print("E: %s" % error, file=sys.stderr)
        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 options.lpapi:
        # try rmadison
        import ubuntutools.requestsync.mail
        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:
            print("E: %s" % error, file=sys.stderr)
            sys.exit(1)

    if ubuntu_version == debian_version:
        print('E: The versions in Debian and Ubuntu are the same already '
              '(%s). Aborting.' % ubuntu_version, file=sys.stderr)
        sys.exit(1)
    if ubuntu_version > debian_version:
        print(('E: The version in Ubuntu (%s) is newer than the version in '
               'Debian (%s). Aborting.')
              % (ubuntu_version, debian_version), file=sys.stderr)
        sys.exit(1)

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

    if not sponsorship and not ffe:
        print('Consider using syncpackage(1) for syncs that do not require '
              'feature freeze exceptions.', file=sys.stderr)

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

    # Generate bug report
    pkg_to_sync = ('%s %s (%s) from Debian %s (%s)'
                   % (srcpkg, debian_version, ubuntu_component,
                      distro, debian_component))
    title = "Sync %s" % pkg_to_sync
    if ffe:
        title = "FFe: " + title
    report = "Please sync %s\n\n" % pkg_to_sync

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

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

    if ffe:
        need_interaction = True

        print('To approve FeatureFreeze exception, you need to state\n'
              'the reason why you feel it is necessary.\n'
              '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 = force_base_version or ubuntu_version

    if newsource:
        report += 'All changelog entries:\n\n'
    else:
        report += ('Changelog entries since current %s version %s:\n\n'
                   % (release, ubuntu_version))
    changelog = debian_srcpkg.getChangelog(since_version=base_version)
    if not changelog:
        if not options.missing_changelog_ok:
            print("E: Did not retrieve any changelog entries. "
                  "Do you need to specify '-C'? "
                  "Was the package recently uploaded? (check "
                  "http://packages.debian.org/changelogs/)", file=sys.stderr)
            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:
        print("E: changelog boilerplate found in report, please manually add "
              "changelog when using '-C'", file=sys.stderr)
        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 = not newsource and srcpkg or None
    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,
                 options.keyid, email_from, mailserver_host, mailserver_port,
                 mailserver_user, mailserver_pass)


if __name__ == '__main__':
    try:
        main()
    except KeyboardInterrupt:
        print("\nUser abort.")
        sys.exit(2)