import hashlib
import json
import logging

from django.contrib.auth.decorators import login_required
from django.http import HttpResponse
from django.views.decorators.http import require_POST
from django.views.decorators.csrf import csrf_exempt

from foldit.models import Score, PuzzleComplete
from student.models import unique_id_for_user

import re

log = logging.getLogger(__name__)


@login_required
@csrf_exempt
@require_POST
def foldit_ops(request):
    """
    Endpoint view for foldit operations.
    """
    responses = []
    if "SetPlayerPuzzleScores" in request.POST:
        puzzle_scores_json = request.POST.get("SetPlayerPuzzleScores")
        pz_verify_json = request.POST.get("SetPlayerPuzzleScoresVerify")
        log.debug("SetPlayerPuzzleScores message: puzzle scores: %r",
                  puzzle_scores_json)

        puzzle_score_verify = json.loads(pz_verify_json)
        if not verifies_ok(request.user.email,
                           puzzle_scores_json, puzzle_score_verify):
            responses.append({"OperationID": "SetPlayerPuzzleScores",
                              "Success": "false",
                              "ErrorString": "Verification failed",
                              "ErrorCode": "VerifyFailed"})
            log.warning("Verification of SetPlayerPuzzleScores failed:" +
                     "user %s, scores json %r, verify %r",
                     request.user, puzzle_scores_json, pz_verify_json)
        else:
            # This is needed because we are not getting valid json - the
            # value of ScoreType is an unquoted string. Right now regexes are
            # quoting the string, but ideally the json itself would be fixed.
            # To allow for fixes without breaking this, the regex should only
            # match unquoted strings,
            a = re.compile(r':([a-zA-Z]*),')
            puzzle_scores_json = re.sub(a, ':"\g<1>",', puzzle_scores_json)
            puzzle_scores = json.loads(puzzle_scores_json)
            responses.append(save_scores(request.user, puzzle_scores))

    if "SetPuzzlesComplete" in request.POST:
        puzzles_complete_json = request.POST.get("SetPuzzlesComplete")
        pc_verify_json = request.POST.get("SetPuzzlesCompleteVerify")

        log.debug("SetPuzzlesComplete message: %r",
                  puzzles_complete_json)

        puzzles_complete_verify = json.loads(pc_verify_json)

        if not verifies_ok(request.user.email,
                           puzzles_complete_json, puzzles_complete_verify):
            responses.append({"OperationID": "SetPuzzlesComplete",
                              "Success": "false",
                              "ErrorString": "Verification failed",
                              "ErrorCode": "VerifyFailed"})
            log.warning("Verification of SetPuzzlesComplete failed:" +
                     " user %s, puzzles json %r, verify %r",
                     request.user, puzzles_complete_json, pc_verify_json)
        else:
            puzzles_complete = json.loads(puzzles_complete_json)
            responses.append(save_complete(request.user, puzzles_complete))

    return HttpResponse(json.dumps(responses))


def verify_code(email, val):
    """
    Given the email and passed in value (str), return the expected
    verification code.
    """
    # TODO: is this the right string?
    verification_string = email.lower() + '|' + val
    return hashlib.md5(verification_string).hexdigest()


def verifies_ok(email, val, verification):
    """
    Check that the hash_str matches the expected hash of val.

    Returns True if verification ok, False otherwise
    """
    if verification.get("VerifyMethod") != "FoldItVerify":
        log.debug("VerificationMethod in %r isn't FoldItVerify", verification)
        return False
    hash_str = verification.get("Verify")

    return verify_code(email, val) == hash_str


def save_scores(user, puzzle_scores):
    score_responses = []
    for score in puzzle_scores:
        log.debug("score: %s", score)
        # expected keys ScoreType, PuzzleID (int),
        # BestScore (energy), CurrentScore (Energy), ScoreVersion (int)

        puzzle_id = score['PuzzleID']
        best_score = score['BestScore']
        current_score = score['CurrentScore']
        score_version = score['ScoreVersion']

        # SetPlayerPuzzleScoreResponse object
        # Score entries are unique on user/unique_user_id/puzzle_id/score_version
        try:
            obj = Score.objects.get(
                user=user,
                unique_user_id=unique_id_for_user(user),
                puzzle_id=puzzle_id,
                score_version=score_version)
            obj.current_score = current_score
            obj.best_score = best_score

        except Score.DoesNotExist:
            obj = Score(
                user=user,
                unique_user_id=unique_id_for_user(user),
                puzzle_id=puzzle_id,
                current_score=current_score,
                best_score=best_score,
                score_version=score_version)
        obj.save()

        score_responses.append({'PuzzleID': puzzle_id,
                                'Status': 'Success'})

    return {"OperationID": "SetPlayerPuzzleScores", "Value": score_responses}


def save_complete(user, puzzles_complete):
    """
    Returned list of PuzzleIDs should be in sorted order (I don't think client
    cares, but tests do)
    """
    for complete in puzzles_complete:
        log.debug("Puzzle complete: %s", complete)
        puzzle_id = complete['PuzzleID']
        puzzle_set = complete['Set']
        puzzle_subset = complete['SubSet']

        # create if not there
        PuzzleComplete.objects.get_or_create(
            user=user,
            unique_user_id=unique_id_for_user(user),
            puzzle_id=puzzle_id,
            puzzle_set=puzzle_set,
            puzzle_subset=puzzle_subset)

    # List of all puzzle ids of intro-level puzzles completed ever, including on this
    # request
    # TODO: this is just in this request...

    complete_responses = list(pc.puzzle_id
                              for pc in PuzzleComplete.objects.filter(user=user))

    return {"OperationID": "SetPuzzlesComplete", "Value": complete_responses}
