#!/usr/bin/python3
# coding: utf-8

# archversion - Archlinux Version Controller
# Copyright © 2012 Sébastien Luttringer
#
# 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.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.

'''Archlinux Version Controller'''

from archversion import VERSION, CONFIG_SENDMAIL
from archversion.config import BaseConfigFile
from archversion.error import BaseError, MissingConfigFile, NoSuchFile
from archversion.error import ERR_FATAL, ERR_ABORT
from archversion.pacman import parse_pkgbuild, pkgbuild_set_version, pkgbuild_update_checksums
from archversion.version import VersionController
from email.mime.text import MIMEText
from email.utils import formatdate
from io import StringIO
from pprint import pprint
from smtplib import SMTP, SMTP_SSL
import argparse
import logging
import os
import sys

def parse_argv():
    '''Parse command line arguments'''
    p_main = argparse.ArgumentParser()
    p_main.add_argument("--version", action="version",
                        version="%(prog)s version " + VERSION)
    p_main.add_argument("--debug", action="store_true",
                        help="debug mode")
    sp_main = p_main.add_subparsers()
    # config parser
    p_conf = sp_main.add_parser("config",
                                help="list configured packages")
    p_conf.add_argument("-s", "--sort", action="store_true",
                        help="sort listing")
    p_conf.add_argument("packages", nargs='*', help="only check these packages")
    p_conf.set_defaults(func=command_config)
    # sync parser
    p_sync = sp_main.add_parser("sync",
                                help="retrieve upstream and dowstream versions")
    p_sync.add_argument("-s", "--sort", action="store_true",
                        help="sort syncing")
    p_sync.add_argument("packages", nargs='*', help="only sync these packages")
    p_sync.set_defaults(func=command_sync)
    # modes parser
    p_modes = sp_main.add_parser("modes",
                                 help="list check against modes")
    p_modes.set_defaults(func=command_modes)
    # report parser
    p_report = sp_main.add_parser("report",
                                 help="report packages versions")
    p_report.add_argument("-f", "--fresh", action="store_true",
                         help="Only report fresh versions")
    p_report.add_argument("-n", "--new", action="store_true",
                         help="Only report new versions")
    p_report.add_argument("-s", "--sort", action="store_true",
                         help="sort packages by name")
    p_report.add_argument("-S", "--sync", action="store_true",
                         help="sync packages versions before report")
    p_report.add_argument("packages", nargs='*',
                         help="only report these packages")
    p_report.set_defaults(func=command_report)
    # check parser
    p_check = sp_main.add_parser("check",
                                 help="check packages versions")
    p_check.add_argument("-f", "--fresh", action="store_true",
                         help="Only report fresh versions")
    p_check.add_argument("-n", "--new", action="store_true",
                         help="Only report new versions")
    p_check.add_argument("-s", "--sort", action="store_true",
                         help="sort packages by name")
    p_check.add_argument("packages", nargs='*',
                         help="only check these packages")
    p_check.set_defaults(func=command_check)
    # sendmail parser
    p_sendmail = sp_main.add_parser("sendmail",
                                 help="sendmail packages versions by mail")
    p_sendmail.add_argument("-f", "--fresh", action="store_true",
                         help="Only sendmail fresh versions")
    p_sendmail.add_argument("-n", "--new", action="store_true",
                         help="Only sendmail new versions")
    p_sendmail.add_argument("-s", "--sort", action="store_true",
                         help="sort packages by name")
    p_sendmail.add_argument("-S", "--sync", action="store_true",
                         help="sync packages versions before sendmail")
    p_sendmail.add_argument("--to", help="mail destination address")
    p_sendmail.add_argument("--smtp", help="smtp server")
    p_sendmail.add_argument("packages", nargs='*',
                         help="only sendmail these packages")
    p_sendmail.set_defaults(func=command_sendmail)
    # update parser
    p_update = sp_main.add_parser("update",
                                  help="update a PKGBUILD with the latest version")
    p_update.add_argument("-p", "--path", default="PKGBUILD",
                          help="name of the file to update. Default PKGBUILD")
    p_update.add_argument("-v", "--vars", action="store_true",
                          help="print variables extracted from the PKGBUILD")
    p_update.add_argument("-c", "--checksum", action="store_true",
                          help="run updpkgsums after update")
    p_update.add_argument("-S", "--no-sync", action="store_true",
                           help="don't sync packages versions prior update")
    p_update.set_defaults(func=command_update)
    # do parse
    namespace = p_main.parse_args()
    # Ensure subparser was choosen
    if "func" not in namespace:
        p_main.error("missing argument")
    return namespace

def command_config(args, vctrl):
    '''list configured packages'''
    if len(args.packages) > 0:
        vctrl.select(args.packages)
    if args.sort:
        vctrl.sort()
    vctrl.print_names()

def command_modes(args, vctrl):
    '''list checking against modes'''
    vctrl.print_modes()

def command_sync(args, vctrl):
    '''Handle sync command call'''
    # reduce the package list
    if len(args.packages) > 0:
        vctrl.select(args.packages)
    # sort packages if asked
    if args.sort:
        vctrl.sort()
    # start syncing
    vctrl.sync()

def command_check(args, vctrl):
    '''Handle check command call'''
    # reduce the package list
    args.sync=True
    command_report(args, vctrl)

def command_report(args, vctrl):
    '''Handle report command call'''
    # reduce the package list
    if len(args.packages) > 0:
        vctrl.select(args.packages)
    # sort packages if asked
    if args.sort:
        vctrl.sort()
    # sync if asked
    if args.sync:
        vctrl.sync()
    # start report
    vctrl.print_versions(args.new, args.fresh)

def command_sendmail(args, vctrl):
    '''Handle sendmail command call'''
    # load sendmail config
    config = BaseConfigFile(CONFIG_SENDMAIL)
    # check args
    try:
        to = config["mail"]["to"]
        from_ = config["mail"].get("from", "archversion")
        subject = config["mail"].get("subject", "Archversion Report")
        host = config["smtp"]["host"]
        port = config["smtp"].get("port", "25")
        login = config["smtp"].get("login")
        password = config["smtp"].get("password")
        tls = config["smtp"].get("tls", "no").lower()
    except KeyError as exp:
        raise BaseError("Unable to load sendmail config") from exp
    # check tls param
    tls_values = ("yes", "no", "starttls")
    if tls not in tls_values:
        raise BaseError("Invalid SMTP tls value: %s. Should be %s." % (tls, "|".join(tls_values)))
    # capture a report
    stdout = StringIO()
    stdout_bak = sys.stdout
    sys.stdout = stdout
    command_report(args, vctrl)
    sys.stdout = stdout_bak
    # no data, no mail!
    if len(stdout.getvalue()) == 0:
        return
    # format the mail
    msg = MIMEText(stdout.getvalue())
    msg["Subject"] = subject
    msg["From"] = from_
    msg["To"] = to
    msg["Date"] = formatdate(localtime=True)
    # send the mail
    try:
        con = SMTP_SSL() if tls == "yes" else SMTP()
        # since python3.7 we need to set host to establish ssl/tls connections
        # this cannot be done in SMTP class contructor because it try to
        # initiate the connection at that time
        con._host = host
        con.connect(host, port)
        if tls == "starttls":
            con.starttls()
        if login:
            con.login(login, password)
        con.send_message(msg)
        con.quit()
    except Exception as exp:
        raise BaseError("Unable to send mail") from exp

def command_update(args, vctrl):
    '''Handle update command call'''
    if not os.path.exists(args.path):
        raise NoSuchFile(args.path)
    if os.getresuid()[1] == 0:
        logging.warn("Warning: You should not run this as root")
    pkgdict = parse_pkgbuild(args.path)
    if args.vars:
        pprint(pkgdict)
    pkgbase = pkgdict.get("pkgbase", None)
    pkgname0 = pkgdict.get("pkgname0", None)
    pkgver = pkgdict.get("pkgver", None)
    # some sanity checks
    if pkgname0 is None and pkgbase is None:
        raise BaseError("Unable to detect pkgname/pkgbase in %s" % args.path)
    if pkgver is None:
        raise BaseError("Unable to detect pkgver in %s" % args.path)
    if pkgname0 in vctrl.packages:
        pkgname = pkgname0
    elif pkgbase in vctrl.packages:
        pkgname = pkgbase
    else:
        raise BaseError("No registered package %s" % (pkgbase or pkgname0))
    # redure packge list to the extracted one
    vctrl.select((pkgname,))
    # sync if not refused
    if not args.no_sync:
        vctrl.sync()
    # get upstream version
    upver = vctrl.versions[pkgname][0]
    if upver is None:
        raise BaseError("Unable to detect upstream version of %s" % pkgname)
    # print what we detect
    print("Package name: %s" % pkgname)
    print("PKGBUILD version: %s" % pkgver)
    print("Upstream version: %s" % upver)
    # compare version
    if pkgver == upver:
        print("Version are the same. Do nothing!")
        return
    # update version with upstream
    pkgbuild_set_version(args.path, upver)
    # update checksum
    if args.checksum:
        pkgbuild_update_checksums(args.path)

def main():
    '''Program entry point'''
    try:
        # parse command line
        args = parse_argv()
        # set global debug mode
        if args.debug:
            logging.getLogger().setLevel(logging.DEBUG)
        # load controller
        vctrl = VersionController()
        # call command function
        return args.func(args, vctrl)
    except KeyboardInterrupt:
        exit(ERR_ABORT)
    except Exception as exp:
        logging.critical(exp)
        if logging.getLogger().getEffectiveLevel() == logging.DEBUG:
            raise
        exit(ERR_FATAL)

if __name__ == '__main__':
    main()

# vim:set ts=4 sw=4 et ai:
