# Copyright (C) 2017 The Meme Factory, Inc.  http://www.meme.com/
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

# Karl O. Pinc <kop@meme.com>

'''
Get the spreadsheet containing the file system conventions.
'''

import errno
import os

import googleapiclient.discovery
import googleapiclient.errors
import httplib2
import oauth2client.client
import oauth2client.service_account

from . import exceptions as ex


# Constants

# rules_source config values
GOOGLE_SOURCE = 'google_drive_oauth2_v3_service_account_json'
FILE_SOURCE = 'file'


# Initialization
DRIVE_API = 'drive'
DRIVE_VERSION = 'v3'
SCOPES = (
    # Allow reading of all files and metadata
    'https://www.googleapis.com/auth/drive.readonly',
)
SRC_MIMETYPE = 'application/vnd.google-apps.spreadsheet'
DST_MIMETYPE = 'text/csv'


#
# Functions
#

def google_credentials(json_file):
    try:
        return (oauth2client.service_account.ServiceAccountCredentials
                .from_json_keyfile_name(json_file, SCOPES))
    except IOError as err:
        if err.errno == errno.ENOENT:
            raise ex.NoGoogleCredentialsFileError(
                'The JSON Google credentials file "{0}" was not found.'
                .format(json_file))
        elif err.errno == errno.EACCES or err.errno == errno.EPERM:
            raise ex.InsufficientPermissionsError(
                'The JSON Google credentials file "{0}" could not be accessed'
                .format(json_file))
        else:
            raise
    except ValueError as err:
        raise ex.NotAServiceAccountError(
            ('File "{0}" does not contain Google service account credentials'
             .format(json_file)),
            'Only Google Service Account credentials are supported')
    except KeyError as err:
        raise ex.InvalidGoogleCredentialsError(
            'File "{0}" is missing the key ({1})'.format(json_file, err))


def authorize_connection(creds):
    try:
        return creds.authorize(httplib2.Http())
    except oauth2client.client.Error as err:
        raise ex.GoogleAuthorizationError(err)


def build_drive(creds, authorize):
    try:
        return googleapiclient.discovery.build(
            DRIVE_API, DRIVE_VERSION, http=authorize(creds))
    except (googleapiclient.errors.Error, oauth2client.client.Error) as err:
        raise ex.GoogleDriveConnectionError(err)


def get_drive_file_info(drive, name):
    try:
        # Don't worry about pagination.  Because the results are sorted
        # we use only the first result.
        return (drive.files()
                .list(
                    q="name='{}' and mimeType='{}' and trashed=false".format(
                        name, SRC_MIMETYPE),
                    orderBy='modifiedTime desc',
                    fields=('files(id,name,version,description,'
                            'modifiedTime,lastModifyingUser,owners)'))
                .execute().get('files', []))
    except (googleapiclient.errors.Error, oauth2client.client.Error) as err:
        if (isinstance(err, googleapiclient.errors.HttpError) and
                err.resp['status'] == '400'):
            raise ex.InvalidGoogleListCallError(err)
        else:
            raise ex.GoogleListError(err)


def download_sheet(drive, id, name):
    try:
        data = (drive.files()
                .export(fileId=id, mimeType=DST_MIMETYPE)
                .execute())
    except (googleapiclient.errors.Error, oauth2client.client.Error) as err:
        raise ex.GoogleDownloadError(err)

    if data:
        return data.decode('ascii', 'ignore')
    else:
        raise ex.NoSheetDownloadedError(
            'Google id ({0}) discovered from name ({1})'.format(id, name),
            'An empty sheet can cause this error')


def google_metainfo(file):
    return (GOOGLE_SOURCE, file)


def rules_from_google(json_creds, authorize, name):
    creds = google_credentials(json_creds)
    try:
        google_drive = build_drive(creds, authorize)
        files = get_drive_file_info(google_drive, name)
    except httplib2.HttpLib2Error as err:
        raise ex.HttpLib2Error(err)

    if files:
        file = files[0]
        return (google_metainfo(file),
                (download_sheet(google_drive, file['id'], name)
                 .splitlines(True)))
    else:
        raise ex.NoSuchSpreadsheetError(
            ('Attempted to download the spreadsheet named ({0})'
             .format(name)),
            ('Perhaps the name is wrong or the credentials '
             'do not have the necessary permissions'))


def file_metainfo(path):
    return (FILE_SOURCE, {'path': os.path.abspath(path),
                          'mtime': os.path.getmtime(path)})


def rules_from_file(path):
    try:
        rule_file = open(path)
    except IOError as err:
        if err.errno == errno.ENOENT:
            raise ex.NoRulesFileError(
                'The rule convention file "{0}" was not found'
                .format(path))
        elif err.errno == errno.EACCES or err.errno == errno.EPERM:
            raise ex.InsufficientPermissionsError(
                'The rule convention file "{0}" could not be accessed'
                .format(path))
        else:
            raise

    metainfo = file_metainfo(path)
    with rule_file:
        content = list(rule_file)

    return (metainfo, content)


def get_rule_file(config):
    if config['rules_source'] == FILE_SOURCE:
        return rules_from_file(config['rules_path'])
    else:
        return rules_from_google(
            config['google_credentials'],
            authorize_connection,
            config['spreadsheet_name'])
