"""
Usage
-----
Query Lambdas are named parameterized and versioned SQL queries
stored in Rockset and can be executed through a dedicated HTTPS endpoint.

Example
-------
::

    from rockset import Client, Q

    # connect securely to Rockset
    rs = Client()

    # create a Query Lambda
    qlambda = rs.QueryLambda.create(
        'myQueryLambda',
        query=Q('SELECT 1'))

    # print details about Query Lambda
    print(qlambda.workspace, qlambda.name, qlambda.version, qlambda.query)

    # execute a Query Lambda
    results = qlambda.execute()
    print(results)

.. _QueryLambda.create:

Create a new Query Lambda
-------------------------
Creating a Query Lambda using the Client_ object is as simple as
calling ``rs.QueryLambda.create('myQueryLambda', query=Q('SELECT...'))``::

    from rockset import Client, Q, F, P
    rs = Client()

    # construct a parameterized query
    q = Q('_events').where(F['type'] == P['target_type']).limit(100)

    # set default value for query parameter
    q.P['target_type'] = 'INFO'

    # create a new Query Lambda
    qlambda = rs.QueryLambda.create(
        'myQueryLambda',
        workspace='commons',
        query=q,
    )

.. _QueryLambda.list:

List all Query Lambdas
----------------------
List all Query Lambdas using the Client_ object using::

    from rockset import Client
    rs = Client()

    # List latest version of all Query Lambdas across all workspaces
    qlambdas = rs.QueryLambda.list()

    # List latest version of all Query Lambdas in a given workspace
    qlambdas = rs.QueryLambda.list(workspace='commons')

.. _QueryLambda.retrieve:

Retrieve an existing Query Lambda version
-----------------------------------------
Retrieve a particular version of a given Query Lambda::

    from rockset import Client
    rs = Client()

    # retrieve Query Lambda
    qlambda = rs.QueryLambda.retrieve(
        'myQueryLambda',
        version=1,
        workspace='commons')

.. _QueryLambda.describe:

Describe an existing Query Lambda version
-----------------------------------------
Fetch all details about a particular version of a given Query Lambda::

    from rockset import Client
    rs = Client()

    # retrieve Query Lambda
    qlambda = rs.QueryLambda.retrieve(
        'myQueryLambda',
        version=1,
        workspace='commons')

    # print all details about this Query Lambda version
    print(qlambda.describe())

.. _QueryLambda.execute:

Execute a specfic Query Lambda version
--------------------------------------
Execute a Query Lambda version, optionally passing in parameters::

    from rockset import Client, ParamDict
    rs = Client()

    # retrieve Query Lambda
    qlambda = rs.QueryLambda.retrieve(
        'myQueryLambda',
        version=1,
        workspace='commons')

    params = ParamDict()
    for target_type in ['INFO', 'DEBUG', 'WARN', 'ERROR']:
        params['target_type'] = target_type
        for result in qlambda.execute(parameters=params).results:
            print(target_type, result)

.. _QueryLambda.update:

Update a Query Lambda by creating a new version
-----------------------------------------------
Update the query associated with the Query Lambda by creating a
new version of it::

    from rockset import Client, Q, F, P, ParamDict
    rs = Client()

    # retrieve Query Lambda
    ql = rs.QueryLambda.retrieve(
        'myQueryLambda',
        version=2,
        workspace='commons')

    # construct a new query
    q = (Q('_events')
        .where(
            (F['type'] == P['target_type']) &
            (F['userEmail'] == P['user_email'])
        )
        .limit(100)
    )

    # optionally, set default value for some or all of the query parameters
    q.P['target_type'] = 'INFO'

    # update Query Lambda
    ql_new_version = ql.update(q)

    # execute the new Query Lambda for different user_emails
    params = ParamDict()
    for email in ['scott@rockset.com', 'veeve@rockset.com']:
        params['user_email'] = email
        results = ql_new_version.execute(params).results
        print(email, results)

.. _QueryLambda.history:

List all versions of a Query Lambda
-----------------------------------
Fetch all versions of a given Query Lambda::

    from rockset import Client
    rs = Client()

    # retrieve Query Lambda
    ql = rs.QueryLambda.retrieve(
        'myQueryLambda',
        version=2,
        workspace='commons')

    # fetch all versions of this Query Lambda
    all_qlambda_versions = ql.history()

.. _QueryLambda.drop:

Drop Query Lambda along with all previous versions
--------------------------------------------------
Use the ``drop()`` method to remove a Query Lambda permanently from Rockset.

.. note:: This is a permanent and non-recoverable operation. Beware.

::

    from rockset import Client
    rs = Client()

    qlambda = rs.QueryLambda.retrieve(
        'myQueryLambda',
        version=1,
        workspace='commons')
    qlambda.drop()

"""
from rockset.query import Q
from rockset.swagger_client.api import (QueryLambdasApi)
from rockset.swagger_client.models import (
    QueryLambda, QueryLambdaSql, CreateQueryLambdaRequest,
    UpdateQueryLambdaRequest, ExecuteQueryLambdaRequest, QueryParameter
)

import rockset


def _convert_query_to_qlsql(query):
    if not isinstance(query, rockset.Query):
        raise ValueError(
            "Input query of type `{}` is not supported. "
            "Please pass a valid rockset.Query object as input.".format(
                type(query)
            )
        )

    (sqltext, sqlparams) = query.sql()
    ql_params = []
    for param in sqlparams.sqlparams():
        ql_params.append(QueryParameter(**param))
    return QueryLambdaSql(query=sqltext, default_parameters=ql_params)


class QueryLambdaClient(object):
    def __init__(self, client):
        self._api = QueryLambdasApi(client)

    def create(
        self, name, query, workspace="commons", description=None, **kwargs
    ):
        # convert rockset.Query to QueryLambdaSql
        ql_sql = _convert_query_to_qlsql(query)

        # construct CreateQueryLambdaRequest
        request = CreateQueryLambdaRequest(
            name=name,
            description=description,
            sql=ql_sql,
        )

        # issue create api call
        ql_model = self._api.create(workspace=workspace,
                                    body=request).get('data')
        return QueryLambda(self._api, ql_model)

    def list(self, workspace=None):
        if workspace:
            # If workspace specified, list queries in that workspace
            ql_models = self._api.list_0(workspace=workspace).get('data')
        else:
            # Else list all queries across all workspaces in the org
            ql_models = self._api.list().get('data')
        return [QueryLambda(self._api, qlm) for qlm in ql_models]

    def retrieve(self, name, version=None, workspace='commons', **kwargs):
        if not version:
            raise ValueError('Query Lambda version not specified.')
        ql_model = self._api.get(
            workspace=workspace, query_lambda=name, version=version
        ).get('data')
        return QueryLambda(self._api, ql_model)


class QueryLambda(rockset.swagger_client.models.QueryLambda):
    def __init__(self, api, model):
        super(QueryLambda, self).__init__(**model.to_dict())
        self._api = api
        self._model = model

        # pull out the required fields explicitly
        self._query = Q(model.sql.query)
        for param in model.sql.default_parameters:
            self._query.P[param.name] = param.value

    @property
    def query(self):
        """Returns the query as a rockset.Query object along with
        default parameters specified for this Query Lambda version.

        Returns:
            Query: a Query object that represents the backend query
            that will be run when this Query Lambda is executed.
        """
        return self._query

    def describe(self):
        """Returns all properties of the Query Lambda as a dict.

        Returns:
            dict: with all properties of the Query Lambda
        """
        return self.to_dict()

    def history(self):
        """Returns the entire version history of this Query Lambda.

        Returns:
            list: List of QueryLambda objects each representing a particular
            version of this Query Lambda
        """
        ql_models = self._api.list_1(
            workspace=self.workspace, query_lambda=self.name
        ).get('data')
        return [QueryLambda(self._api, qlm) for qlm in ql_models]

    def update(self, query, description=None):
        """Updates the query associated with thie Query Lambda and returns
        a new QueryLambda object that represents the newly created version of
        this Query Lambda.

        Args:
           query (Query): Query object using which a new version of this
           Query Lambda will be created.
           description (str): Human readable description that can be used as
           a commit message for this update.

        Returns:
            QueryLambda: a QueryLambda object that represents the new version
        """
        ql_sql = _convert_query_to_qlsql(query)
        request = UpdateQueryLambdaRequest(sql=ql_sql, description=description)
        ql_model = self._api.update(
            workspace=self.workspace, query_lambda=self.name, body=request
        ).get('data')
        return QueryLambda(self._api, ql_model)

    def execute(self, parameters=None, **kwargs):
        """Execute this Query Lambda using the given parameters and
        return a Cursor object to iterate through the results.

        If the Query Lambda was created without any parameters
        then no parameters need to be specified.

        If the Query Lambda was created with 1 or more query parameters,
        then all of those parameters can be specified as input.

        If a query parameter was not specified and a default value was
        provided for that query parameter when that Query Lambda version was
        created, then the default value will be used to execute the query.

        If a required query parameter was not specified,
        then an InputError Exception will be raised.

        Args:
            parameters (ParamDict): a ParamDict instance with values bound
            for all required query parameters, which will be used to
            execute this Query Lambda.

        Returns:
            Cursor: returns a cursor that can fetch query results
        """
        all_params = None
        if parameters:
            if not isinstance(parameters, rockset.ParamDict):
                raise ValueError(
                    "Query parameters of type `{}` is not supported. "
                    "Please pass a valid rockset.ParamDict object as input.".
                    format(type(parameters))
                )
            all_params = []
            for param in parameters.sqlparams():
                all_params.append(QueryParameter(**param))
        request = ExecuteQueryLambdaRequest(parameters=all_params, **kwargs)
        # TODO TODO TODO -- turn this into a Cursor
        return self._api.execute(
            workspace=self.workspace,
            query_lambda=self.name,
            version=self.version,
            body=request,
        )

    def drop(self):
        """Deletes the Query Lambda along with the current and all previous
        versions. Beware, this will permanently delete the Query Lambda and
        will be unrecoverable.
        """
        return self._api.delete(self.workspace, self.name).get('data')
