#!/usr/bin/env python
# -*- coding: utf-8 -*-

# This file is part of Pymetrick.  The COPYRIGHT file at the top level of
# this repository contains the full copyright notices and license terms.

"""Modulo para gestionar S3 BUCKETS de AWS o compatibles"""

try:
    from pymetrick.version import __author__, __version__, __license__, __copyright__
except ImportError:
    raise ImportError(
        'Failed to find (autogenerated) version.py '
    )

__date__ = '2012-09-21'
__credits__ = ''
__text__ = 'Tratamiento de S3 Buckets'
__file__ = 'bucket.py'
__boto3_version__ = '1.26.84'

#--- CHANGES ------------------------------------------------------------------
# 2021-09-14 v0.01 PL: - First version

import sys
# sin bytecode
sys.dont_write_bytecode = True

#from secrets import AWS_DEFAULT_REGION, AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, endpoint_url

import botocore
import boto3
from boto3.s3.transfer import S3Transfer, TransferConfig

import os
import queue

import logging
import logging.handlers

try:
    import simplejson as json
except:
    import json

'''
DEBUG - debug message
INFO - info message
WARNING - warn message
ERROR - error message
CRITICAL - critical message
'''
LOG_LEVEL_VALUE = {'DEBUG':10,'INFO':20,'WARNING':30,'ERROR':40,'CRITICAL':50}
if str(os.environ.get('PYMETRICK_LOG_LEVEL',None)).upper() in LOG_LEVEL_VALUE:
    LOG_LEVEL = LOG_LEVEL_VALUE[str(os.environ.get('PYMETRICK_LOG_LEVEL')).upper()]
else:
    LOG_LEVEL = 30
LOG_FILENAME = '-'.join([os.path.abspath(__file__).split(os.sep)[len(os.path.abspath(__file__).split(os.sep))-1],])[:-3]
LOG = logging.getLogger(LOG_FILENAME)

if 'LD_LIBRARY_PATH' in list(os.environ.keys()):
    # CGI environment
    sys.stdout = sys.stderr
    logging.basicConfig(stream = sys.stderr, level=LOG_LEVEL, format='%(filename)s[line:%(lineno)d] - %(levelname)s - %(message)s')
else:
    # not CGI environment
    logging.basicConfig(stream=sys.stderr)
    hdlr = logging.handlers.RotatingFileHandler(filename=LOG_FILENAME+'.log',mode='a', encoding='utf-8', maxBytes=1048576, backupCount=3)
    formatter = logging.Formatter('%(asctime)s - %(filename)s[line:%(lineno)d] - %(levelname)s - %(message)s')
    hdlr.setFormatter(formatter)
    LOG.addHandler(hdlr)
    LOG.setLevel(LOG_LEVEL)
    
GB = 1024 ** 3

class Bucket(object):
    '''Gestion de S3 Buckets'''

    def __init__(self, *args,**kwargs):
        '''
        @parameters
          aws_access_key_id
          aws_secret_access_key
          endpoint_url
        '''
        try:
            # Define the configuration rules
            self.cors_configuration = {
                    'CORSRules': [{
                    'AllowedHeaders': ['Authorization'],
                    'AllowedMethods': ['GET', 'PUT', 'POST', 'DELETE'],
                    'AllowedOrigins': ['*'],
                    'ExposeHeaders': ['ETag', 'x-amz-request-id'],
                    'MaxAgeSeconds': 3000
                }]
            }

            if 'default_bucket' in kwargs:
                self.default_bucket = kwargs['default_bucket']
            else:
                self.default_bucket = None

            object_storage_config = {
            'aws_access_key_id' : None,
            'aws_secret_access_key' : None,
            'endpoint_url': None }

            for key in object_storage_config.keys():
                if key in kwargs:
                    object_storage_config[key] = kwargs[key]
                elif key == 'cors':
                    self.cors_configuration = cors if (isinstance(cors,(dict,)) and 'CORSRules' in cors) else None
                    
                
            self.client = boto3.client('s3', **object_storage_config )
            self.resource = boto3.resource('s3', **object_storage_config)

            if self.default_bucket and self.cors_configuration:
                self.client.put_bucket_cors(Bucket=self.default_bucket, CORSConfiguration=self.cors_configuration)

        except botocore.exceptions.ClientError as error:
            for key, value in sorted(botocore.exceptions.__dict__.items()):
                if isinstance(value, type):
                    print(key)
        except botocore.exceptions.ParamValidationError as error:
            raise ValueError('The parameters you provided are incorrect: {}'.format(error))


    def list_buckets(self):
        '''
        list buckets
        '''
        try:
            list_buckets = []
            response = self.client.list_buckets()
            if isinstance(response,dict) and "HTTPStatusCode" in response["ResponseMetadata"] and response["ResponseMetadata"]["HTTPStatusCode"]==200:
                if 'Buckets' in response:
                    for bucket in response['Buckets']:
                        list_buckets.append(bucket['Name'])
                return list_buckets
            else:
                return ['Error']

        except botocore.exceptions.ClientError as error:
            for key, value in sorted(botocore.exceptions.__dict__.items()):
                if isinstance(value, type):
                    print(key)
        except botocore.exceptions.ParamValidationError as error:
            raise ValueError('The parameters you provided are incorrect: {}'.format(error))

    def versioning(self, version=False):
        '''enabling|suspend versioning for s3 bucket object'''
        try:
            _status_init = False
            _status_final = False 
            if self.default_bucket:
                _versioning = self.resource.BucketVersioning(self.default_bucket)
                _status_init = _versioning.status

                if version:
                    _versioning.enable()
                else:
                    _versioning.suspend()

                _status_final = _versioning.status

                return [_status_init, _status_final]
            
        except botocore.exceptions.ClientError as error:
            for key, value in sorted(botocore.exceptions.__dict__.items()):
                if isinstance(value, type):
                    print(key)
        except botocore.exceptions.ParamValidationError as error:
            raise ValueError('The parameters you provided are incorrect: {}'.format(error))                    
            

    def list_objects(self, prefix='', maxkeys=1000):
        try:

            list_objects = []
            
            # 10000 max result line for request
            response = self.client.list_objects_v2(Bucket=self.default_bucket, MaxKeys=maxkeys, Prefix=prefix)
            if isinstance(response,dict) and "HTTPStatusCode" in response["ResponseMetadata"] and response["ResponseMetadata"]["HTTPStatusCode"]==200:
                if 'Contents' in response:
                    for key in response['Contents']:
                        list_objects.append(key['Key'])

                while response['IsTruncated'] and maxkeys>=1000:
                    continuation_key = response['NextContinuationToken']
                    response = self.client.list_objects_v2(Bucket=self.default_bucket, MaxKeys=maxkeys, Delimiter="/", ContinuationToken=continuation_key, Prefix=prefix)
                    if 'Contents' in response:
                        for key in response['Contents']:
                            list_objects.append(key['Key'])
                            if maxkeys != 1000:
                                if len(list_objects)==maxkeys:
                                    break
                    
                return list_objects
            else:
                return ['Error']
            

        except botocore.exceptions.ClientError as error:
            for key, value in sorted(botocore.exceptions.__dict__.items()):
                if isinstance(value, type):
                    print(key)
        except botocore.exceptions.ParamValidationError as error:
            raise ValueError('The parameters you provided are incorrect: {}'.format(error))


    def add_bucket(self, bucket_name=None ):
        try:
            if bucket_name:
                self.client.create_bucket(Bucket=bucket_name)
                self.default_bucket = bucket_name
        except botocore.exceptions.ClientError as error:
            for key, value in sorted(botocore.exceptions.__dict__.items()):
                if isinstance(value, type):
                    print(key)
        except botocore.exceptions.ParamValidationError as error:
            raise ValueError('The parameters you provided are incorrect: {}'.format(error))


    def delete_bucket(self, bucket_name=None ):
        try:
            if bucket_name:
                self.client.delete_bucket(Bucket=bucket_name)
            else:
                self.client.delete_bucket(Bucket=self.default_bucket)

        except botocore.exceptions.ClientError as error:
            for key, value in sorted(botocore.exceptions.__dict__.items()):
                if isinstance(value, type):
                    print(key)
        except botocore.exceptions.ParamValidationError as error:
            raise ValueError('The parameters you provided are incorrect: {}'.format(error))


    def upload_object(self, prefix='', list_objects=[], extra_args=None):
        ''' Upload a file to an S3 bucket
            @parameters
            prefix: Bucket path
            list_objects: S3 list object name
            extra_args: dict with 'extra_args' like extra_args={'ACL':'public-read'} or
            extra_args={'GrantRead': 'uri="http://acs.amazonaws.com/groups/global/AllUsers"',
                'GrantFullControl': 'id="01234567890abcdefg"',}
            extra_args={'ACL': 'public-read', 'ContentType': 'image/png'}

            @return
            :True if file was uploaded, else False
        '''
        try:
            transfer = S3Transfer(self.client)
            config = TransferConfig(multipart_threshold=5*GB)
            for file in list_objects:
                upload_file_key = (''.join([prefix,os.path.split(file)[-1:][0]])).upper()
                transfer.upload_file(
                                  filename=file,
                                  bucket=self.default_bucket,
                                  key=upload_file_key,
                                  extra_args=extra_args)
                
            return True
        except botocore.exceptions.ClientError as error:
            for key, value in sorted(botocore.exceptions.__dict__.items()):
                if isinstance(value, type):
                    LOG.error(f'''bucket.upload_object {key}''')
            return False
        except botocore.exceptions.ParamValidationError as error:
            LOG.error(ValueError('The parameters you provided are incorrect: {}'.format(error)))
            raise ValueError('The parameters you provided are incorrect: {}'.format(error))
            return False
        except Exception as e:
            if hasattr(e, 'message'):
                LOG.error('<%s> in line %s !!!' % (e.message,format(sys.exc_info()[-1].tb_lineno)))
            else:
                LOG.error('<%s> in line %s !!!' % (e,format(sys.exc_info()[-1].tb_lineno)))
            return False

    #from pathlib import Path
    def download_object(self, prefix='', list_objects=[]):
        try:
            objects = []
            transfer = S3Transfer(self.client)
            if len(list_objects)==0:
                list_objects = self.client.list_objects(prefix=prefix)
                prefix=None
            for file in list_objects:
                if prefix:
                    download_file_key = (''.join([prefix,os.path.split(file)[-1:][0]])).upper()
                else:
                    download_file_key = file.upper()
                path, filename = os.path.split(file.upper())
                #Create sub directories if its not existing
                if not os.path.exists(path):
                    try:
                        os.makedirs(path)
                    except OSError:
                        if not os.path.isdir(path):
                            raise
                    #Path(path).mkdir(parents=True, exist_ok=True)
                try:
                    transfer.download_file(
                        bucket=self.default_bucket,
                        key=download_file_key,
                        filename=os.sep.join([path,filename]))
                    objects.append(os.sep.join([path,filename]))
                except Exception as e:
                    print(e)
            return objects

        except botocore.exceptions.ClientError as error:
            for key, value in sorted(botocore.exceptions.__dict__.items()):
                if isinstance(value, type):
                    print(key)
        except botocore.exceptions.ParamValidationError as error:
            raise ValueError('The parameters you provided are incorrect: {}'.format(error))
        except Exception as e:
            print(e)


    def delete_object(self, prefix=None, list_objects=[]):
        try:
            if len(list_objects)==0:
                list_objects = self.client.list_objects(prefix=prefix)
                prefix=None                
            for file in list_objects:
                # delete object o directory
                if prefix:
                    delete_file_key = (''.join([prefix,os.path.split(file)[-1:][0]])).upper()
                else:
                    delete_file_key = file.upper()
                response = self.client.delete_object(
                    Bucket=self.default_bucket,
                    Key=delete_file_key)
                if 'DeleteMarker' in response:
                    return response['DeleteMarker']
                else:
                    return False
        except botocore.exceptions.ClientError as error:
            for key, value in sorted(botocore.exceptions.__dict__.items()):
                if isinstance(value, type):
                    print(key)
        except botocore.exceptions.ParamValidationError as error:
            raise ValueError('The parameters you provided are incorrect: {}'.format(error))

    def backup_object(self, prefix=''):
        try:
            _prefix = prefix
            _list_objects = self.list_objects(prefix=_prefix)
            return self.download_object(prefix=None,list_objects=_list_objects)
        except botocore.exceptions.ClientError as error:
            for key, value in sorted(botocore.exceptions.__dict__.items()):
                if isinstance(value, type):
                    print(key)
        except botocore.exceptions.ParamValidationError as error:
            raise ValueError('The parameters you provided are incorrect: {}'.format(error))        
        


if __name__  == "__main__":
    print ('''copyright {0}'''.format( __copyright__))
    print ('''license {0}'''.format( __license__))
    print ('''version {0}'''.format( __version__))
    if len(sys.argv) < 2:
        sys.stderr.write("for help use -h o --help")
    elif sys.argv[1]=='-h' or sys.argv[1]=='--help':
        print ('''
        Tratamiento de S3 Bucket\n\n''')


