import datetime
import logging
import os
import typing
from decimal import Decimal

from django.http import HttpResponse
from django.utils import formats, timezone
from rest_framework.exceptions import APIException

_log = logging.getLogger(__name__)


def catch_exception(func):
    """
        Catches any exceptions raised in the function.
        If the exception is a subclass of APIException, reraise it to be handled by DRF.
        If not a subclass of APIException, then raise an APIException so that DRF handles it anyway and shows human error
    """

    def wrapper(*args, **kwargs):
        try:
            return func(*args, **kwargs)
        except APIException as e:
            raise e
        except Exception as e:
            _log.error(f'Error: {str(e)}')
            raise APIException(detail={
                'status': 'error',
                'code': 'unknown',
                'detail': f'{str(e)}'
            })

    return wrapper


def dtnow(days=0, seconds=0, microseconds=0, milliseconds=0, minutes=0, hours=0, weeks=0,
          tz: timezone = timezone.utc, local=False) -> datetime.datetime:
    """ returns current datetime or datetime in future/past by time units
        use positive/negative time units for future/past datetime
    :param tz: timezone or None to use datetime without timezone, can not be None if local parameter is True
    :param local: True to change returning value timezone to user local
    :return: datetime.datetime object
    """
    if not tz:  # make sure None is used for disabling and only for nonlocal dates
        tz = timezone.utc if local else None
    ret = datetime.datetime.now(tz=tz) + datetime.timedelta(days=days, seconds=seconds, microseconds=microseconds,
                                                            milliseconds=milliseconds, minutes=minutes, hours=hours,
                                                            weeks=weeks)
    return timezone.localtime(ret) if local else ret


def fmt_input(
        val: typing.Union[str, bool, int, float, Decimal, datetime.datetime, datetime.date, datetime.time],
        format: str = None) -> str:
    """ Make localized string for specified input value using specified format
    :param val: value to format, may be of different types
    :param format: format for data or None to use default localized format
    :return: localized data as str
    """
    return formats.localize_input(val, format)


def store_file_response(file_name):
    with open(file_name, 'rb') as fh:
        return httpFileResponse(fh.read(), os.path.basename(file_name))


def httpFileResponse(content, fname='file', zip=None):
    response = HttpResponse(content, content_type="application/file")
    fname = fname.replace('"', '').replace("'", '')
    if zip:
        response['Content-Disposition'] = f'attachment; filename="{fname}.zip"'
    else:
        response['Content-Disposition'] = f'attachment; filename="{fname}"'
    return response
