#  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/{wheel_id}/participant', methods=['PUT', 'POST'])
def create_participant(event):
    """
    Create a participant

    :param event: Lambda event containing the API Gateway request body including a name and a url and the
    path parameter wheel_id
    {
      "pathParameters":
      {
        "wheel_id": string ID of the wheel (DDB Hash Key)
      },
      "body":
      {
        "name": participant name string,
        "url: Valid URL for the participant,
      }
    }
    :return: response dictionary containing new participant object if successful
    {
      "body":
      {
        "id": string ID of the participant (DDB Hash Key),
        "wheel_id": string ID of the wheel (DDB Hash Key),
        "name": string name of the wheel,
        "url: URL for the participant,
        "created_at": creation timestamp,
        "updated_at": updated timestamp,
      }
    }
    """
    wheel_id = event['pathParameters']['wheel_id']
    body = event['body']
    if not check_string(body.get('name', None)) or not check_string(body.get('url', None)):
        raise base.BadRequestError("Participants require a name and url which must be at least 1 character in length")

    wheel = Wheel.get_existing_item(Key={'id': wheel_id})
    create_timestamp = get_utc_timestamp()

    participant = {
        'wheel_id': wheel_id,
        'id': get_uuid(),
        'name': body['name'],
        'url': body['url'],
        'created_at': create_timestamp,
        'updated_at': create_timestamp,
    }
    with choice_algorithm.wrap_participant_creation(wheel, participant):
        WheelParticipant.put_item(Item=participant)
    return participant


@base.route('/wheel/{wheel_id}/participant/{participant_id}', methods=['DELETE'])
def delete_participant(event):
    """
    Deletes the participant from the wheel and redistributes wheel weights

    :param event: Lambda event containing the API Gateway request path parameters wheel_id and participant_id
    {
      "pathParameters":
      {
        "wheel_id": string ID of the wheel (DDB Hash Key)
        "participant_id": string ID of the participant (DDB Hash Key)
      },
    }
    :return: response dictionary
    """
    wheel_id = event['pathParameters']['wheel_id']
    participant_id = event['pathParameters']['participant_id']
    # Make sure the wheel exists
    wheel = Wheel.get_existing_item(Key={'id': wheel_id})

    # REST-ful Deletes are idempotent and should not error if it's already been deleted
    response = WheelParticipant.delete_item(Key={'wheel_id': wheel_id, 'id': participant_id}, ReturnValues='ALL_OLD')
    if 'Attributes' in response:
        choice_algorithm.on_participant_deletion(wheel, response['Attributes'])


@base.route('/wheel/{wheel_id}/participant', methods=['GET'])
def list_participants(event):
    """
    Gets the participants for the specified 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 a list of participants
    {
      "body":
      [
        participant1,
        participant2,
        ...
        participantn,
      ]
    }
    """
    wheel_id = event['pathParameters']['wheel_id']
    # Make sure the wheel exists
    Wheel.get_existing_item(Key={'id': wheel_id})
    return list(WheelParticipant.iter_query(KeyConditionExpression=Key('wheel_id').eq(wheel_id)))


@base.route('/wheel/{wheel_id}/participant/{participant_id}', methods=['PUT', 'POST'])
def update_participant(event):
    """
    Update a participant's name and/or url

    :param event: Lambda event containing the API Gateway request body including updated name or url and the
    path parameters wheel_id and participant_id
    {
      "pathParameters":
      {
        "wheel_id": string ID of the wheel (DDB Hash Key)
        "participant_id": string ID of the participant (DDB Hash Key)
      },
      "body":
      {
        "id": string ID of the participant (DDB Hash Key),
        "name": string name of the wheel (optional),
        "url: Valid URL for the participant (optional),
      }
    }
    :return: response dictionary containing the updated participant object if successful
    {
      "body":
      {
        "id": string ID of the participant (DDB Hash Key),
        "wheel_id": string ID of the wheel (DDB Hash Key),
        "name": string name of the wheel,
        "url: URL for the participant,
        "created_at": creation timestamp,
        "updated_at": updated timestamp,
      }
    }
    """
    wheel_id = event['pathParameters']['wheel_id']
    participant_id = event['pathParameters']['participant_id']
    # Check that the participant exists
    participant = WheelParticipant.get_existing_item(Key={'id': participant_id, 'wheel_id': wheel_id})
    body = event['body']
    params = {'updated_at': get_utc_timestamp()}
    if not check_string(body.get('name', 'Not Specified')) or not check_string(body.get('url', 'Not Specified')):
        raise base.BadRequestError("Participants names and urls must be at least 1 character in length")

    if 'name' in body:
        params['name'] = body['name']

    if 'url' in body:
        params['url'] = body['url']

    WheelParticipant.update_item(Key={'id': participant_id, 'wheel_id': wheel_id}, **to_update_kwargs(params))
    participant.update(params)
    return participant


@base.route('/wheel/{wheel_id}/participant/{participant_id}/select', methods=['PUT', 'POST'])
def select_participant(event):
    """
    Indicates selection of a participant by the wheel.  This will cause updates to the weights for all participants
    or removal of rigging if the wheel is rigged.

    :param event: Lambda event containing the API Gateway request path parameters wheel_id and participant_id
    {
      "pathParameters":
      {
        "wheel_id": string ID of the wheel to rig (DDB Hash Key)
        "participant_id": string ID of the participant to rig (DDB Hash Key)
      },
    }
    :return: response dictionary
    """
    wheel_id = event['pathParameters']['wheel_id']
    participant_id = event['pathParameters']['participant_id']
    wheel = Wheel.get_existing_item(Key={'id': wheel_id})
    participant = WheelParticipant.get_existing_item(Key={'id': participant_id, 'wheel_id': wheel_id})
    choice_algorithm.select_participant(wheel, participant)

    # Undo any rigging that has been set up
    Wheel.update_item(Key={'id': wheel['id']}, UpdateExpression='remove rigging')


@base.route('/wheel/{wheel_id}/participant/{participant_id}/rig', methods=['PUT', 'POST'])
def rig_participant(event):
    """
    Rig the specified wheel for the specified participant.  Default behavior is comical rigging (hidden == False)
    but hidden can be specified to indicate deceptive rigging (hidden == True)

    :param event: Lambda event containing the API Gateway request path parameters wheel_id and participant_id
    {
      "pathParameters":
      {
        "wheel_id": string ID of the wheel to rig (DDB Hash Key)
        "participant_id": string ID of the participant to rig (DDB Hash Key)
      },
      "body":
      {
        "hidden": boolean indicates deceptive rigging if True, comical if False
      }
    }
    :return: response dictionary
    """
    # By default, rigging the wheel isn't hidden but they can be
    wheel_id = event['pathParameters']['wheel_id']
    participant_id = event['pathParameters']['participant_id']
    hidden = bool(event['body'].get('hidden', False))
    update = {'rigging': {'participant_id': participant_id, 'hidden': hidden}}
    Wheel.update_item(Key={'id': wheel_id}, **to_update_kwargs(update))


@base.route('/wheel/{wheel_id}/participant/suggest', methods=['GET'])
def suggest_participant(event):
    """
    Returns a suggested participant to be selected by the next wheel spin

    :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 a selected participant_id
    {
      "body":
      {
        "participant_id": string ID of the suggested participant (DDB Hash Key),
        "rigged": True (if rigged, otherwise this key is not present)
      }
    }
    """
    wheel_id = event['pathParameters']['wheel_id']
    wheel = Wheel.get_existing_item(Key={'id': wheel_id})
    if 'rigging' in wheel:
        participant_id = wheel['rigging']['participant_id']
        # Use rigging only if the rigged participant is still available
        if 'Item' in WheelParticipant.get_item(Key={'wheel_id': wheel_id, 'id': participant_id}):
            return_value = {'participant_id': participant_id}
            # Only return rigged: True if we're not using hidden rigging
            if not wheel['rigging'].get('hidden', False):
                return_value['rigged'] = True
            return return_value
    return {'participant_id': choice_algorithm.suggest_participant(wheel)}
