#  Copyright 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved.
#
#  Licensed under the Apache License, Version 2.0 (the "License").
#  You may not use this file except in compliance with the License.
#  A copy of the License is located at
#
#      http://aws.amazon.com/apache2.0/
#
#  or in the "license" file accompanying this file. This file is distributed
#  on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
#  express or implied. See the License for the specific language governing
#  permissions and limitations under the License.

from boto3.dynamodb.conditions import Key
from utils import get_utc_timestamp, get_uuid, Wheel, WheelParticipant, check_string, to_update_kwargs
import base
import choice_algorithm


@base.route('/wheel', methods=['PUT', 'POST'])
def create_wheel(event):
    """
    Create a wheel. Requires a name

    :param event: Lambda event containing the API Gateway request body including a name
    {
      "body":
      {
        "name": string wheel name,
      }
    }
    :return: response dictionary containing new wheel object if successful
    {
      "body":
      {
        "id": string ID of the wheel (DDB Hash Key),
        "name": string name of the wheel,
        "participant_count": number of participants in the wheel,
        "created_at": creation timestamp,
        "updated_at": updated timestamp,
      }
    }
    """
    create_timestamp = get_utc_timestamp()
    body = event['body']
    if body is None or not check_string(body.get('name', None)):
        raise base.BadRequestError(
            f"New wheels require a name that must be a string with a length of at least 1.  Got: {body}"
        )

    wheel = {
        'id': get_uuid(),
        'name': body['name'],
        'created_at': create_timestamp,
        'updated_at': create_timestamp,
    }
    with choice_algorithm.wrap_wheel_creation(wheel):
        Wheel.put_item(Item=wheel)
    return wheel


@base.route('/wheel/{wheel_id}', methods=['DELETE'])
def delete_wheel(event):
    """
    Deletes the wheel and all of its participants

    :param event: Lambda event containing the API Gateway request path parameter wheel_id
    {
      "pathParameters":
      {
        "wheel_id": string ID of the wheel (DDB Hash Key)
      }
    }
    :return: response dictionary
    """
    wheel_id = event['pathParameters']['wheel_id']
    # DynamoDB always succeeds for delete_item,
    Wheel.delete_item(Key={'id': wheel_id})

    # Clear out all participants of the wheel.  Query will be empty if it was already deleted
    with WheelParticipant.batch_writer() as batch:
        query_params = {
            'KeyConditionExpression': Key('wheel_id').eq(wheel_id),
            'ProjectionExpression': 'id'
        }
        # We don't use the default generator here because we don't want the deletes to change the query results
        for p in list(WheelParticipant.iter_query(**query_params)):
            batch.delete_item(Key={'id': p['id'], 'wheel_id': wheel_id})


@base.route('/wheel/{wheel_id}', methods=['GET'])
def get_wheel(event):
    """
    Returns the wheel object corresponding to the given wheel_id

    :param event: Lambda event containing the API Gateway request path parameter wheel_id
    {
      "pathParameters":
      {
        "wheel_id": string ID of the wheel (DDB Hash Key)
      }
    }
    :return: response dictionary containing the requested wheel object if successful
    {
      "body":
      {
        "id": string ID of the wheel (DDB Hash Key),
        "name": string name of the wheel,
        "participant_count": number of participants in the wheel,
        "created_at": creation timestamp,
        "updated_at": updated timestamp,
      }
    }
    """
    return Wheel.get_existing_item(Key={'id': event['pathParameters']['wheel_id']})


@base.route('/wheel', methods=['GET'])
def list_wheels(event):
    """
    Get all available wheels

    :param event: Lambda event containing query string parameters that are passed to Boto's scan() API for the wheel
    table
    {
      "queryStringParameters":
      {
        ...
      }
    }
    :return: List of wheels
    {
      "body":
        "Count": number of wheels,
        "Items":
        [
          wheel1,
          wheel2,
          wheeln,
        ],
        "ScannedCount": number of items before queryStringParameters were applied,
      }
    }
    """
    parameters = event.get('queryStringParameters', None) or {}
    return Wheel.scan(**parameters)


@base.route('/wheel/{wheel_id}', methods=['PUT', 'POST'])
def update_wheel(event):
    """
    Update the name of the wheel and/or refresh its participant count

    :param event: Lambda event containing the API Gateway request path parameter wheel_id
    {
      "pathParameters":
      {
        "wheel_id": string ID of the wheel (DDB Hash Key)
      },
      "body":
      {
        "id": string ID of the wheel (DDB Hash Key),
        "name": string name of the wheel,
      }
    }
    :return: response dictionary containing the updated wheel object if successful
    {
      "body":
      {
        "id": string ID of the wheel (DDB Hash Key),
        "name": string name of the wheel,
        "participant_count": number of participants in the wheel,
        "created_at": creation timestamp,
        "updated_at": updated timestamp,
      }
    }
    """
    wheel_id = event['pathParameters']['wheel_id']
    key = {'id': wheel_id}
    # Make sure wheel exists
    wheel = Wheel.get_existing_item(Key=key)
    name = event['body'].get('name', None)
    if not check_string(name):
        raise base.BadRequestError("Updating a wheel requires a new name of at least 1 character in length")

    update = {'name': name, 'updated_at': get_utc_timestamp()}
    Wheel.update_item(Key=key, **to_update_kwargs(update))
    # Represent the change locally for successful responses
    wheel.update(update)
    return wheel


@base.route('/wheel/{wheel_id}/reset', methods=['PUT', 'POST'])
def reset_wheel(event):
    """
    Resets the weights of all participants of the wheel

    :param event: Lambda event containing the API Gateway request path parameter wheel_id
    {
      "pathParameters":
      {
        "wheel_id": string ID of the wheel (DDB Hash Key)
      }
    }
    :return: response dictionary
    """
    # Ensure that the wheel exists
    wheel_id = event['pathParameters']['wheel_id']
    wheel = Wheel.get_existing_item(Key={'id': wheel_id})
    choice_algorithm.reset_wheel(wheel)


@base.route('/wheel/{wheel_id}/unrig', methods=['PUT', 'POST'])
def unrig_participant(event):
    """
    Remove rigging for the specified wheel

    :param event: Lambda event containing the API Gateway request path parameter wheel_id
    {
      "pathParameters":
      {
        "wheel_id": string ID of the wheel (DDB Hash Key)
      }
    }
    :return: response dictionary
    """
    # By default, rigging the wheel isn't hidden but they can be
    wheel_id = event['pathParameters']['wheel_id']

    Wheel.update_item(Key={'id': wheel_id}, UpdateExpression='remove rigging')
