# Mycroft Server - Backend
# Copyright (C) 2019 Mycroft AI Inc
# SPDX-License-Identifier: 	AGPL-3.0-or-later
#
# This file is part of the Mycroft Server.
#
# The Mycroft Server is free software: you can redistribute it and/or
# modify it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
"""Step functions for adding an account via the single sign on API."""
from binascii import b2a_base64
from datetime import datetime

from behave import given, then, when  # pylint: disable=no-name-in-module
from flask import json
from hamcrest import assert_that, equal_to, is_in, not_none

from selene.data.account import AccountRepository, PRIVACY_POLICY, TERMS_OF_USE
from selene.data.metric import AccountActivityRepository


@given("a user completes new account setup")
def build_new_account_request(context):
    """Build base request data for the new account tests."""
    context.new_account_request = dict(
        termsOfUse=True,
        privacyPolicy=True,
        login=dict(
            federatedPlatform=None,
            federatedToken=None,
            email=b2a_base64(b"bar@mycroft.ai").decode(),
            password=b2a_base64(b"bar").decode(),
        ),
    )


@given("user does not include {required_field}")
def remove_required_field(context, required_field):
    """Remove the specified field from the new account request."""
    if required_field == "an email address":
        del context.new_account_request["login"]["email"]
    elif required_field == "a password":
        del context.new_account_request["login"]["password"]
    elif required_field == "an accepted Terms of Use":
        del context.new_account_request["termsOfUse"]
    elif required_field == "an accepted Privacy Policy":
        del context.new_account_request["privacyPolicy"]


@given("user does not agree to the {agreement}")
def reject_agreement(context, agreement):
    """Set the status of the specified agreement to rejected."""
    if agreement == "Terms of Use":
        context.new_account_request["termsOfUse"] = False
    elif agreement == "Privacy Policy":
        context.new_account_request["privacyPolicy"] = False


@when("the new account request is submitted")
def call_add_account_endpoint(context):
    """Call the single sign on API endpoint to create a new account"""
    context.client.content_type = "application/json"
    response = context.client.post(
        "/api/account",
        data=json.dumps(context.new_account_request),
        content_type="application/json",
    )
    context.response = response


@when("a user enters an email address already in use")
def call_validate_email_endpoint(context):
    """Call the single sign on API endpoint to validate an email address."""
    existing_account = context.accounts["foobar"]
    email_address = existing_account.email_address.encode()
    token = b2a_base64(email_address).decode()

    context.client.content_type = "application/json"
    response = context.client.get(
        f"/api/validate-email?platform=Internal&token={token}"
    )
    context.response = response


@then("the account will be added to the system")
def check_db_for_account(context):
    """Check that the account was created as expected."""
    acct_repository = AccountRepository(context.db)
    account = acct_repository.get_account_by_email("bar@mycroft.ai")
    # add account to context so it will deleted by cleanup step
    context.accounts["bar"] = account
    assert_that(account, not_none())
    assert_that(account.email_address, equal_to("bar@mycroft.ai"))

    assert_that(len(account.agreements), equal_to(2))
    utc_date = datetime.utcnow().date()
    for agreement in account.agreements:
        assert_that(agreement.type, is_in((PRIVACY_POLICY, TERMS_OF_USE)))
        assert_that(agreement.accept_date, equal_to(str(utc_date)))


@then("the new account will be reflected in the account activity metrics")
def check_db_for_account_metrics(context):
    """Ensure the new account is accurately reflected in the metrics."""
    acct_activity_repository = AccountActivityRepository(context.db)
    account_activity = acct_activity_repository.get_activity_by_date(
        datetime.utcnow().date()
    )
    if context.account_activity is None:
        assert_that(account_activity.accounts_added, equal_to(1))
    else:
        assert_that(
            account_activity.accounts_added,
            equal_to(context.account_activity.accounts_added + 1),
        )


@then("a duplicate email address error is returned")
def check_for_duplicate_account_error(context):
    """Check the API response for an "account exists" error."""
    response = context.response
    assert_that(response.json["accountExists"], equal_to(True))
