#!/usr/bin/env python
import argparse
import sys

from gi.repository import Secret

_SCHEMA_DICT = {
    # Nominally non-secret fields that can be used for filtering
    "dbname": Secret.SchemaAttributeType.STRING,
    "host": Secret.SchemaAttributeType.STRING,
    "hostaddr": Secret.SchemaAttributeType.STRING,
    "port": Secret.SchemaAttributeType.STRING,
    "user": Secret.SchemaAttributeType.STRING,
    "sslmode": Secret.SchemaAttributeType.STRING,
    "shorthand": Secret.SchemaAttributeType.STRING,
}

SCHEMA = Secret.Schema.new("org.postgresql.Password",
                           Secret.SchemaFlags.NONE,
                           _SCHEMA_DICT)


def conninfo_to_dict(conninfo):
    out = {}
    parts = conninfo.split(' ')

    for maybe_pair in parts:
        keyword, value = maybe_pair.split('=')
        out[keyword] = value

    return out


def store(conninfo, label, shorthand):
    # Parse the input and scrub the sensitive information.
    info = conninfo_to_dict(conninfo)
    info.pop('password', None)

    if shorthand:
        info['shorthand'] = shorthand

    already_present = Secret.password_lookup_sync(SCHEMA, info, None)
    if already_present:
        print >>sys.stderr, ('Adding this secret would be ambiguous with '
                             'another keychain entry with the attributes: {0}'
                             .format(info))

    Secret.password_store_sync(SCHEMA, info, Secret.COLLECTION_DEFAULT,
                               label, conninfo, None)


def recall(conninfo):
    # First look for a shorthand, using that if it matches.
    shorthand_resolve = Secret.password_lookup_sync(
        SCHEMA, {'shorthand': conninfo}, None)

    if shorthand_resolve:
        return shorthand_resolve

    # No shorthand: try to match the record based on common libpq
    # connection string keywords and values.
    if '=' not in conninfo:
        # Presume is a naked dbname.
        conninfo = 'dbname=' + conninfo

    info = conninfo_to_dict(conninfo)

    if 'password' in info:
        # If a secret was specified, don't use the keychain
        return conninfo
    else:
        # Get the secret from the keychain
        found = Secret.password_lookup_sync(SCHEMA, info, None)

        if not found:
            # Return untouched if the keyring could not be employed.
            return conninfo

        return found


def main(argv):
    """Store and load secrets for PostgreSQL databases.

    This is done via libsecret/FreeDesktop.org secret-service
    primitives.
    """

    parser = argparse.ArgumentParser(
        formatter_class=argparse.RawDescriptionHelpFormatter,
        description=__doc__)

    subparsers = parser.add_subparsers(title='subcommands', dest='subcommand')

    # Add 'store' action
    store_parser = subparsers.add_parser(
        'store', help='store a postgresql secret taken on stdin')

    store_parser.add_argument('--label', '-l', help='human readable label',
                              default='PostgreSQL Database Information')
    store_parser.add_argument('--shorthand', '-s',
                              help='short name for the database')

    # Add 'recall' action
    subparsers.add_parser(
        'recall',
        help=('recall a postgresql secret, '
              'using the standard input as the search parameter'))

    # Set-up complete, parse the arguments
    args = parser.parse_args(argv)

    # Dispatch on the parsed subcommand
    if args.subcommand == 'store':
        store(sys.stdin.read(), args.label, args.shorthand)
        return 0
    elif args.subcommand == 'recall':
        sys.stdout.write(recall(sys.stdin.read()))
        return 0

if __name__ == '__main__':
    sys.exit(main(sys.argv[1:]))
