# Created by Noé Cruz | Zurckz 22 at 20/03/2022
# See https://www.linkedin.com/in/zurckz
from typing import Callable, Optional, Any, Union
from zpy.app import zapp_context as ctx
import json
import copy


class SQSHandler(object):

    def __init__(self, processor: Optional[Callable[[dict, dict], Any]], single: Optional[bool] = False,
                 strict=False, verbose: bool = False, jsonfy=True, re_enqueue: bool = False):
        self.processor = processor
        self.single_item = single
        self.strict = strict
        self.verbose = verbose
        self.response_builder = None
        self.request_parser = None
        self.error_notifier = None
        self.starting_precess_logger = None
        self.jsonfy = jsonfy
        self.re_enqueue = re_enqueue

    @classmethod
    def of(cls, processor: Optional[Callable[[dict, dict], Any]], single: Optional[bool] = False, strict=False,
           verbose=False, jsonfy=True):
        return cls(processor, single, strict, verbose, jsonfy)

    @staticmethod
    def proxy(event, handler):
        return handler.run(event)

    def __notify_error(self, record, msg):
        if self.error_notifier:
            self.error_notifier(record, msg)

    def run(self, event):
        logger = ctx().logger
        failed = False

        if self.request_parser:
            event = self.request_parser(event)

        if not self.processor:
            msg = "The sqs record processor cant be null."
            self.__notify_error(None, msg)
            return msg

        if 'Records' not in event:
            if self.strict:
                msg = "Fatal :: Could not continue processing, invalid event!"
                self.__notify_error(event, msg)
                logger.err(msg)
                return msg
            event = {'Records': [event]}
        batch_results = []
        for index, record in enumerate(event["Records"]):
            body: Optional[Union[dict, str]] = None
            x_record = copy.copy(record)
            record_id = index if 'messageId' not in record else record['messageId']
            try:
                if 'body' not in record:
                    msg = "Fatal :: Invalid item, could not continue processing..."
                    self.__notify_error(x_record, msg)
                    if self.strict:
                        if self.single_item:
                            return msg
                        continue
                    body = record
                else:
                    body = record.pop('body')
                if type(body) is not dict:
                    if self.jsonfy:
                        body = json.loads(body)
                if self.verbose:
                    logger.info(f"Starting record processing: {record_id} with data: {body}")
                if self.starting_precess_logger:
                    self.starting_precess_logger(body, record)
                result = self.processor(body, record)
                if self.verbose:
                    logger.info(result)
                batch_results.append({
                    "result": result,
                    "message_id": record_id
                })
                if self.single_item:
                    break
            except Exception as e:
                failed = True
                self.__notify_error(x_record, "Fatal :: An error occurred while processing notification data")
                logger.exception(f"An error occurred while processing notification data: {body}\n", exc_info=e)
                if self.re_enqueue:
                    raise Exception("Exception autogenerated for re enqueue record...")
        response = {
            'message': "Finished Process." if not failed else "Finished process with errors.",
            "results": batch_results
        }
        if self.response_builder:
            response = self.response_builder(response)
        return response

    def single(self, body, context):
        ...
