#!/usr/bin/env python
"""
stormpath-export
~~~~~~~~~~~~~~~~

Easily export your Stormpath (https://stormpath.com/) user data.

Usage:
  stormpath-export configure
  stormpath-export [(<location> | -l <location> | --location <location>)]
  stormpath-export (-h | --help)
  stormpath-export --version

Options:
  -h --help  Show this screen.
  --version  Show version.

Written by Randall Degges <http://www.rdegges.com/>.
"""


from json import dumps, loads
from os import chmod, getcwd, makedirs
from os.path import dirname, exists, expanduser
from sys import exit

from docopt import docopt
from stormpath.client import Client
from stormpath.error import Error


##### GLOBALS
CONFIG_FILE = expanduser('~/.stormy')
VERSION = 'stormpath-export 0.0.3'


class StormpathExport(object):
    """Our CLI manager."""

    EXPORTS = ['applications', 'directories', 'groups', 'accounts']

    def __init__(self):
        """Initialize our Stormpath client, or die tryin' >:)"""
        if exists(CONFIG_FILE):
            credentials = loads(open(CONFIG_FILE, 'rb').read())
            self.client = Client(api_key={
                'id': credentials.get('stormpath_api_key_id'),
                'secret': credentials.get('stormpath_api_key_secret'),
            })
        else:
            print 'No API credentials found!  Please run stormpath-export configure to set them up.'
            exit(1)

    def set_location(self, location):
        """
        Return the proper location used to export our JSON data.

        :param str location: The location to backup all Stormpath data (must be
            a folder).
        """
        if not location:
            location = getcwd() + '/stormpath-exports'

        return location

    def write(self, file, data):
        """
        Write JSON data to the specified file.

        This is a simple wrapper around our file handling stuff.

        :param str file: The file to write.
        :param dict data: The data to write to the file, as a JSON dict.
        """
        if not exists(dirname(file)):
            makedirs(dirname(file))

        with open(file + '.json', 'wb') as file:
            file.write(dumps(data, indent=2, separators=(',', ': '), sort_keys=True))

    def export_applications(self):
        """Export all application data for this Stormpath account."""
        print '\n=== Exporting all application data...'

        for application in self.client.applications:
            print '- Exporting application:', application.name

            json = {
                'href': application.href,
                'name': application.name,
                'description': application.description,
                'status': application.status,
                'accounts': [],
                'groups': [],
            }

            for account in application.accounts:
                json['accounts'].append({
                    'directory': account.directory.name,
                    'account': account.email,
                })

            for group in application.groups:
                json['groups'].append({
                    'directory': group.directory.name,
                    'group': group.name,
                })

            self.write('%s/applications/%s' % (self.location, application.name), json)

        print '=== Done!\n'

    def export_groups(self):
        """Export all group data for this Stormpath account."""
        print '=== Exporting all group data...'

        for directory in self.client.directories:
            for group in directory.groups:
                print '- Exporting group:', group.name

                json = {
                    'href': group.href,
                    'name': group.name,
                    'description': group.description,
                    'status': group.status,
                    'customData': dict(group.custom_data),
                    'directory': directory.name,
                    'accounts': [],
                }

                for account in group.accounts:
                    json['accounts'].append(account.email)

                self.write('%s/directories/%s/groups/%s' % (self.location, directory.name, group.name), json)

        print '=== Done!\n'

    def export_accounts(self):
        """Export all account data for this Stormpath account."""
        print '=== Exporting all account data...'

        for directory in self.client.directories:
            for account in directory.accounts:
                print '- Exporting account:', account.email

                json = {
                    'href': account.href,
                    'username': account.username,
                    'email': account.email,
                    'fullName': account.full_name,
                    'givenName': account.given_name,
                    'middleName': account.middle_name,
                    'surname': account.surname,
                    'status': account.status,
                    'customData': dict(account.custom_data),
                    'groups': [],
                    'directory': directory.name,
                }

                for group in account.groups:
                    json['groups'].append(group.name)

                self.write('%s/directories/%s/accounts/%s' % (self.location, directory.name, account.email), json)

        print '=== Done!\n'

    def export_directories(self):
        """Export all directory data for this Stormpath account."""
        print '=== Exporting all directory data...'

        for directory in self.client.directories:
            print '- Exporting directory:', directory.name

            json = {
                'name': directory.name,
                'description': directory.description,
                'href': directory.href,
                'status': directory.status,
                'accounts': [],
                'groups': [],
            }

            for account in directory.accounts:
                json['accounts'].append(account.email)

            for group in directory.groups:
                json['groups'].append(group.name)

            self.write('%s/directories/%s/meta' % (self.location, directory.name), json)

        print '=== Done!\n'

    def export(self, location=None):
        """
        Export all Stormpath data to the disk, in JSON format.

        Takes an optional argument (the directory to export all data to).

        :param str location: The location to backup all Stormpath data (must be
            a folder).
        """
        self.location = self.set_location(location)

        # Export all Stormpath data.
        for export_type in self.EXPORTS:
            getattr(self, 'export_' + export_type)()


def configure():
    """
    Initializing stormpath-export.

    This will store the user's API credentials in: ~/.stormy, and ensure the
    API credentials specified actually work.
    """
    print 'Initializing `stormpath-export`...\n'
    print "To get started, we'll need to get your Stormpath API credentials.  Don't have a Stormpath account?  Go get one!  https://stormpath.com/"

    finished = False
    while not finished:
        api_key_id = raw_input('Enter your API Key ID: ').strip()
        api_key_secret = raw_input('Enter your API Key Secret: ').strip()
        if not (api_key_id or api_key_secret):
            print '\nNot sure how to find your Stormpath API credentials?'
            print 'Log into your Stormpath account, then visit your dashboard and use the "Manage Existing Keys" link.\n'
            continue

        # Validate the API credentials.
        client = Client(api_key={
            'id': api_key_id,
            'secret': api_key_secret,
        })
        try:
            client.applications
            print '\nSuccessfully initialized stormy!'
            print 'Your API credentials are stored in the file:', CONFIG_FILE, '\n'
            print 'Run stormpath-export  for usage information.'

            with open(CONFIG_FILE, 'wb') as stormycfg:
                stormycfg.write(dumps({
                    'stormpath_api_key_id': api_key_id,
                    'stormpath_api_key_secret': api_key_secret,
                }, indent=2, sort_keys=True))

            # Make the stormy configuration file only accessible to the current
            # user -- this makes the credentials a bit more safe.
            chmod(CONFIG_FILE, 0600)

            finished = True
        except Error:
            print '\nYour API credentials are not working, please verify they are correct, then try again.\n'


def main():
    """Handle user input, and do stuff accordingly."""
    arguments = docopt(__doc__, version=VERSION)

    # Handle the configure as a special case -- this way we won't get invalid
    # API credential messages when we're trying to configure stormpath-export.
    if arguments['configure']:
        configure()
        return

    stormy = StormpathExport()
    stormy.export(arguments['<location>'])


if __name__ == '__main__':
    main()
