# Copyright (c) CloudZero - ALL RIGHTS RESERVED - PROPRIETARY AND CONFIDENTIAL
# Unauthorized copying of this file and/or project, via any medium is strictly prohibited.
# Direct all questions to legal@cloudzero.com

import base64
import gzip
from typing import Any

from toolz import dissoc

from czc import json
from czc.api.v2.exceptions import APIErrorType
from czc.logging import LogContext, Logger

DEFAULT_ERROR_MESSAGE = 'API Error'


def _normalize_error_body(status_code: int, body: str | dict[str, Any], error_type: APIErrorType) -> dict[str, Any]:
    try:
        json_body = json.loads(body) if isinstance(body, str) else body
        error = json_body.get('error', json_body)

        if isinstance(error, str):
            error = {
                'message': error,
                'type': error_type,
            }
        elif status_code == 422 and 'detail' in error:
            error = {
                'message': 'The request is invalid. Check the details for more information.',
                'type': error_type,
                'details': error.get('detail'),
            }
    except Exception:
        error = {
            'message': body,
            'type': error_type,
        }

    return {
        'error': {
            'type': error_type,
            **dissoc(error, 'statusCode'),
        },
    }


def http_response(logger: Logger):
    """Make this Lambda Handler handle legacy response dictionaries and
    Pyfaaster Exceptions and API V2 ServiceErrors. This is a drop in replacement
    for pyfaaster.aws.handlers_decorators_v2.http_response.

    Parameters
    ----------

    Returns
    -------
    Callable
        Decorated Lambda Handler
    """

    def http_response_handler(handler):
        def handler_wrapper(event, context, **kwargs):
            error: str | dict[str, Any] = {}
            try:
                with LogContext(logger=logger, method=event.get('httpMethod'), path=event.get('path')):
                    response: dict = handler(event, context, **kwargs)

                status_code = response.get('statusCode', 200)
                if status_code < 300:
                    return response

                if response.get('isBase64Encoded', False):
                    try:
                        encoded_error = base64.b64decode(response['body'])
                        error = gzip.decompress(encoded_error).decode()
                    except Exception:
                        logger.exception('Failed to decompress error body.')
                        error = DEFAULT_ERROR_MESSAGE
                else:
                    error = response.get('body', 'API Error')

                body = _normalize_error_body(status_code, error, APIErrorType.from_status_code(status_code))
                response['body'] = json.dumps(body)
                response = dissoc(response, 'isBase64Encoded')

                if status_code >= 500:
                    logger.error(error)
                elif status_code >= 300:
                    logger.warning(error)
                return response
            except Exception as err:
                status_code = getattr(err, 'status_code', getattr(err, 'statusCode', 500))
                error_type = APIErrorType.from_status_code(status_code)
                if hasattr(err, 'body'):
                    exception_body = getattr(err, 'body')
                    body = _normalize_error_body(status_code, exception_body, error_type)
                else:
                    body = {
                        'error': {
                            'message': DEFAULT_ERROR_MESSAGE if status_code >= 500 else str(err),
                            'type': error_type,
                        }
                    }

                if status_code >= 500:
                    logger.exception(err)
                else:
                    logger.warning(err)

                return {'statusCode': status_code, 'body': json.dumps(body)}

        return handler_wrapper

    return http_response_handler
