"""The Masonite Response Object."""

import json

from masonite.exceptions import ResponseError
from masonite.helpers.Extendable import Extendable
from masonite.view import View

from orator.support.collection import Collection
from orator import Model
from orator import Paginator, LengthAwarePaginator
from masonite.app import App


class Response(Extendable):

    def __init__(self, app: App):
        """A Response object to be used to abstract the logic of getting a response ready to be returned.

        Arguments:
            app {masonite.app.App} -- The Masonite container.
        """
        self.app = app
        self.request = self.app.make('Request')

    def json(self, payload, status=200):
        """Gets the response ready for a JSON response.

        Arguments:
            payload {dict|list} -- Either a dictionary or a list.

        Returns:
            string -- Returns a string representation of the data
        """
        self.app.bind('Response', json.dumps(payload))
        self.make_headers(content_type="application/json; charset=utf-8")
        self.request.status(status)

        return self.data()

    def paginated_json(self, paginator, status=200):
        """Determine type of paginated instance and return JSON response.

        Arguments:
            paginator {Paginator|LengthAwarePaginator} --
                Either an Orator Paginator or LengthAwarePaginator object

        Returns:
            string -- Returns a string representation of the data
        """
        # configured param types
        page_size_parameter = 'page_size'
        page_parameter = 'page'

        # try to capture request input for page_size and/or page
        page_size_input = self.request.input(page_size_parameter)
        page_input = self.request.input(page_parameter)
        # use try/except here, as int(bool) will return 0 for False above
        try:
            page_size = (
                int(page_size_input)
                if page_size_input and int(page_size_input) > 0
                else paginator.per_page
            )
        except Exception:
            page_size = paginator.per_page
        try:
            page = (
                int(page_input)
                if page_input and int(page_input) > 0
                else paginator.current_page
            )
        except Exception:
            page = paginator.current_page

        # don't waste time instantiating new paginator if no change
        if (
            page_size != paginator.per_page
            or page != paginator.current_page
        ):
            try:
                # try to get class of model
                next(type(x) for x in paginator.items)
                if isinstance(paginator, Paginator):
                    paginator = model_class.simple_paginate(
                        page_size,
                        page
                    )
                elif isinstance(paginator, LengthAwarePaginator):
                    paginator = model_class.paginate(page_size, page)
            except Exception:
                paginator = paginator

        payload = {
            'total': (
                paginator.total
                if isinstance(paginator, LengthAwarePaginator)
                else None
            ),
            'count': paginator.count(),
            'per_page': page_size,
            'current_page': page,
            'last_page': (
                paginator.last_page
                if isinstance(paginator, LengthAwarePaginator)
                else None
            ),
            'from': (page_size * (page - 1)) + 1,
            'to': page_size * page,
            'data': paginator.serialize()
        }

        # remove fields not relevant to Paginator instance
        if isinstance(paginator, Paginator):
            del payload['total']
            del payload['last_page']

        return self.json(payload, status)

    def make_headers(self, content_type="text/html; charset=utf-8"):
        """Make the appropriate headers based on changes made in controllers or middleware.

        Keyword Arguments:
            content_type {str} -- The content type to set. (default: {"text/html; charset=utf-8"})
        """
        self.request.header('Content-Length', str(len(self.to_bytes())))

        # If the user did not change it directly
        if not self.request.has_raw_header('Content-Type'):
            self.request.header('Content-Type', content_type)

    def data(self):
        """Get the data that will be returned to the WSGI server.

        Returns:
            string -- Returns a string representation of the response
        """
        if self.app.has('Response'):
            return self.app.make('Response')

        return ''

    def converted_data(self):
        """Converts the data appropriately so the WSGI server can handle it.

        Returns:
            string -- Returns a string representation of the data
        """
        if isinstance(self.data(), (dict, list)):
            return json.dumps(self.data())
        else:
            return self.data()

    def view(self, view, status=200):
        """Set a string or view to be returned.

        Arguments:
            view {string|dict|list|masonite.view.View} -- Some data type that is an appropriate response.

        Keyword Arguments:
            status {int} -- The Response status code. (default: {200})

        Raises:
            ResponseError -- If a data type that is not an acceptable response type is returned.

        Returns:
            string|dict|list -- Returns the data to be returned.
        """
        if not self.request.get_status():
            self.request.status(status)

        if isinstance(view, (dict, list)):
            return self.json(view, status=self.request.get_status())
        elif isinstance(view, (Collection, Model)):
            return self.json(view.serialize(), status=self.request.get_status())
        elif isinstance(view, int):
            view = str(view)
        elif isinstance(view, View):
            view = view.rendered_template
        elif isinstance(view, self.request.__class__):
            view = self.data()
        if isinstance(view, (Paginator, LengthAwarePaginator)):
            return self.paginated_json(view, status=self.request.get_status())
        elif view is None:
            raise ResponseError('Responses cannot be of type: None.')

        if not isinstance(view, str):
            raise ResponseError('Invalid response type of {}'.format(type(view)))

        self.app.bind('Response', view)

        self.make_headers()

        return self.data()

    def redirect(self, location=None, status=302):
        """Set the redirection on the server.

        Keyword Arguments:
            location {string} -- The URL to redirect to (default: {None})
            status {int} -- The Response status code. (default: {302})

        Returns:
            string -- Returns the data to be returned.
        """
        self.request.status(status)
        if not location:
            location = self.request.redirect_url

        self.request.reset_headers()
        self.request.header('Location', location)
        self.app.bind('Response', 'redirecting ...')

        return self.data()

    def to_bytes(self):
        """Converts the data to bytes so the WSGI server can handle it.

        Returns:
            bytes -- The converted response to bytes.
        """
        return bytes(self.converted_data(), 'utf-8')
