from json import dumps
from typing import Dict, List, Optional, Union

from fittrackee import appLog
from flask import Response
from flask_sqlalchemy import SQLAlchemy


def get_empty_data_for_datatype(data_type: str) -> Union[str, List]:
    return '' if data_type in ['gpx', 'chart_data'] else []


class HttpResponse(Response):
    def __init__(
        self,
        response: Optional[Union[str, Dict]] = None,
        status_code: Optional[int] = None,
        content_type: Optional[str] = None,
    ) -> None:
        if isinstance(response, dict):
            response = dumps(response)
            content_type = (
                'application/json' if content_type is None else content_type
            )
        super().__init__(
            response=response,
            status=status_code,
            content_type=content_type,
        )


class GenericErrorResponse(HttpResponse):
    def __init__(
        self, status_code: int, message: str, status: Optional[str] = None
    ) -> None:
        response = {
            'status': 'error' if status is None else status,
            'message': message,
        }
        super().__init__(
            response=response,
            status_code=status_code,
        )


class InvalidPayloadErrorResponse(GenericErrorResponse):
    def __init__(
        self, message: Optional[str] = None, status: Optional[str] = None
    ) -> None:
        message = 'Invalid payload.' if message is None else message
        super().__init__(status_code=400, message=message, status=status)


class DataInvalidPayloadErrorResponse(HttpResponse):
    def __init__(self, data_type: str, status: Optional[str] = None) -> None:
        response = {
            'status': 'error' if status is None else status,
            'data': {data_type: get_empty_data_for_datatype(data_type)},
        }
        super().__init__(response=response, status_code=400)


class UnauthorizedErrorResponse(GenericErrorResponse):
    def __init__(self, message: Optional[str] = None) -> None:
        message = (
            'Invalid token. Please request a new token.'
            if message is None
            else message
        )
        super().__init__(status_code=401, message=message)


class ForbiddenErrorResponse(GenericErrorResponse):
    def __init__(self, message: Optional[str] = None) -> None:
        message = (
            'You do not have permissions.' if message is None else message
        )
        super().__init__(status_code=403, message=message)


class NotFoundErrorResponse(GenericErrorResponse):
    def __init__(self, message: str) -> None:
        super().__init__(status_code=404, message=message, status='not found')


class UserNotFoundErrorResponse(NotFoundErrorResponse):
    def __init__(self) -> None:
        super().__init__(message='User does not exist.')


class DataNotFoundErrorResponse(HttpResponse):
    def __init__(self, data_type: str, message: Optional[str] = None) -> None:
        response = {
            'status': 'not found',
            'data': {data_type: get_empty_data_for_datatype(data_type)},
        }
        if message:
            response['message'] = message
        super().__init__(response=response, status_code=404)


class PayloadTooLargeErrorResponse(GenericErrorResponse):
    def __init__(self, message: str) -> None:
        super().__init__(status_code=413, message=message, status='fail')


class InternalServerErrorResponse(GenericErrorResponse):
    def __init__(
        self, message: Optional[str] = None, status: Optional[str] = None
    ):
        message = (
            'Error. Please try again or contact the administrator.'
            if message is None
            else message
        )
        super().__init__(status_code=500, message=message, status=status)


def handle_error_and_return_response(
    error: Exception,
    message: Optional[str] = None,
    status: Optional[str] = None,
    db: Optional[SQLAlchemy] = None,
) -> HttpResponse:
    if db is not None:
        db.session.rollback()
    appLog.error(error)
    return InternalServerErrorResponse(message=message, status=status)
