# Copyright 2008 The Tor Project, Inc.  See LICENSE for licensing information.
# Copyright 2010 The Update Framework.  See LICENSE for licensing information.

import ConfigParser
import os
import sys

import tuf.formats
import tuf.keys
import tuf.log
import tuf.repo.keystore
import tuf.sig
import tuf.util

logger = tuf.log.get_logger()

json = tuf.util.import_json()


def get_keystore(filename, password=None, encrypted=True):
    return tuf.repo.keystore.KeyStore(filename)


# TODO(jsamuel): Have it raise an exception instead of exiting (or change the
# function name).
def get_key(keydb, keyid):
    if keyid is not None:
        keys = keydb.get_keys_fuzzy(keyid)
    if len(keys) < 1:
        logger.error("No such key.\nI wanted")
        logger.error("keyid='%s...'" % keyid)
        logger.error("I only know about:")
        for k in keydb.iterkeys():
            logger.error("  %s" % k.get_key_id())
        sys.exit(1)
    elif len(keys) > 1:
        logger.error("Multiple keys match.  Possibilities are:")
        for k in keys:
            logger.error("  %s" % k.get_key_id())
        sys.exit(1)
    else:
        return keys[0]


def _read_config_file(filename):
    """Return a dictionary where the keys are section names and the values
       are dictionaries of keys/values in that section.
    """
    config = ConfigParser.RawConfigParser()
    config.read(filename)
    configdict = {}
    for section in config.sections():
        configdict[section] = {}
        for key, value in config.items(section):
            if key in ['threshold', 'seconds', 'minutes', 'days', 'hours']:
                value = int(value)
            elif key in ['keyids']:
                value = value.split(',')
            configdict[section][key] = value
    return configdict


def generate_root_meta(config_file, public_key_keydb):
    """
    Creates the root metadata.
    """
    config = _read_config_file(config_file)

    roledict = {}
    keydict = {}

    for rolename in ['root', 'targets', 'release', 'timestamp']:
        if rolename not in config:
            raise tuf.Error("No '%s' section found in config file." % rolename)
        keyids = []
        for fuzzy_keyid in config[rolename]['keyids']:
            key = get_key(public_key_keydb, keyid=fuzzy_keyid)
            keyid = key.get_key_id()
            if keyid not in keydict:
                keydict[keyid] = key.get_meta()
            if keyid in keyids:
                raise tuf.Error("Same keyid listed twice: %s", keyid)
            keyids.append(keyid)
        role_meta = tuf.formats.make_role_meta(keyids,
                                             config[rolename]['threshold'])
        roledict[rolename] = role_meta

    exp = config['expiration']
    expiration_seconds = (exp['seconds'] + 60 * exp['minutes'] +
                          3600 * exp['hours'] + 3600 * 24 * exp['days'])

    root_meta = tuf.formats.RootFile.make_meta(keydict, roledict,
                                               expiration_seconds)
    return tuf.formats.make_signable(root_meta)


def _get_file_info_meta(filename):
    length, hashes = tuf.util.get_file_details(filename)
    custom = None
    return tuf.formats.make_file_info_meta(length, hashes, custom)


# TODO: 'size' instead of 'length'?


def generate_targets_meta(target_files=None, targets_filedict=None,
                          delegations_keydb=None):
    """
    The targets must exist at the same path they should on the repo.
    
    This takes a list targets. We're not worrying about custom metadata at the
    moment. It's allowed to not provide keys.
    """
    if targets_filedict is None:
        filedict = {}
    else:
        filedict = targets_filedict.copy()

    if target_files is not None:
        for target in target_files:
            filedict[target] = _get_file_info_meta(target)

    delegations_meta = None
    if delegations_keydb is not None:
        keys = {}
        roles = {}
        for rolename in delegations_keydb.get_role_names():
            threshold = delegations_keydb.get_role_threshold(rolename)
            keyids = delegations_keydb.get_role_keyids(rolename)
            targetpaths = delegations_keydb.get_role_paths(rolename)
            for keyid in keyids:
                key = delegations_keydb.get_key(keyid)
                keys[keyid] = key.get_meta(private=False)
            roles[rolename] = {'threshold' : threshold, 'keyids' : keyids,
                               'paths' : targetpaths}
        delegations_meta = {'keys' : keys, 'roles' : roles}

    targets_meta = tuf.formats.TargetsFile.make_meta(
        targets_filedict=filedict, delegations=delegations_meta)

    return tuf.formats.make_signable(targets_meta)


def generate_release_meta(meta_dir):
    """
    The minimum metadata must exist. This is root.txt and targets.txt. This
    will also look for a targets/ delegation metadata directory and the
    resulting release file will list all files in that directory.
    """
    root_filename = os.path.join(meta_dir, 'root.txt')
    targets_filename = os.path.join(meta_dir, 'targets.txt')
    filedict = {}
    filedict['root.txt'] = _get_file_info_meta(root_filename)
    filedict['targets.txt'] = _get_file_info_meta(targets_filename)

    meta_targets_dir = os.path.join(meta_dir, 'targets')
    if os.path.exists(meta_targets_dir) and os.path.isdir(meta_targets_dir):
        for dirpath, _, files in os.walk(meta_targets_dir):
            for basename in files:
                if not dirpath.startswith(meta_dir):
                    raise tuf.Error('Confused while walking targets/')
                meta_path = os.path.join(dirpath, basename)
                meta_name = meta_path[len(meta_dir):].lstrip('/')
                if not meta_name.startswith('targets/'):
                    raise tuf.Error('Confused while figuring out meta_name: %s' % meta_name)
                filedict[meta_name] = _get_file_info_meta(meta_path)

    release_meta = tuf.formats.ReleaseFile.make_meta(filedict)
    return tuf.formats.make_signable(release_meta)


def generate_timestamp_meta(release_filename, compressions=()):
    """
    The release.txt file must exist.
    """
    fileinfo = {}
    fileinfo['release.txt'] = _get_file_info_meta(release_filename)
    for ext in compressions:
        compress_filename = release_filename + '.' + ext
        fileinfo['release.txt.' + ext] = _get_file_info_meta(compress_filename)
    timestamp_meta = tuf.formats.TimestampFile.make_meta(fileinfo)
    return tuf.formats.make_signable(timestamp_meta)


def write_metadata_file(meta, filename):
    logger.info("Writing to %s" % filename)
    f = open(filename, 'w')
    json.dump(meta, f, indent=1, sort_keys=True)
    f.write('\n')
    f.close()


def read_metadata_file(filename):
    return tuf.util.load_json_file(filename)


def sign_meta(meta, keyids, keydb):
    """
    Sign a file. If any of the keyids have already signed the file, the old
    signature will be replaced.
    """
    signable = tuf.formats.make_signable(meta)

    for keyid in keyids:
        key = get_key(keydb, keyid=keyid)
        print("Signing with key %s" % key.get_key_id())
        tuf.sig.add_signature(signable, key)

    tuf.formats.check_signed_obj_format(signable)

    return signable


def verify_meta(meta, keystore):
    """
    Verify signatures on a file. Doesn't verify that they are trusted, just
    that the signatures are valid.
    """
    role = None
    status = tuf.sig.get_signature_status(meta, keystore, role)

    return status


def generate_key(bits=None, keystore=None, password=None):
    key = tuf.keys.RSAKey.generate(bits)
    logger.info("Generated new key: %s" % key.get_key_id())
    if keystore is not None:
        keystore.add_key(key, password)
        keystore.save()
    return key