from unittest.mock import ANY

import pytest
from flask import url_for

from app.models.user import User
from tests.conftest import normalize_spaces


def test_render_register_returns_template_with_form(
    client_request,
):
    client_request.logout()
    page = client_request.get_url("/register")

    assert page.find("input", attrs={"name": "auth_type"}).attrs["value"] == "sms_auth"
    assert page.select_one("#email_address")["spellcheck"] == "false"
    assert page.select_one("#email_address")["autocomplete"] == "email"
    assert page.select_one("#password")["autocomplete"] == "new-password"
    assert "Create an account" in page.text


def test_logged_in_user_redirects_to_account(
    client_request,
):
    client_request.get(
        "main.register",
        _expected_status=302,
        _expected_redirect=url_for("main.show_accounts_or_dashboard"),
    )


@pytest.mark.parametrize(
    "phone_number_to_register_with",
    [
        "+12023900460",
        "+1800-555-5555",
    ],
)
@pytest.mark.parametrize(
    "password",
    [
        "the quick brown fox",
        "   the   quick   brown   fox   ",
    ],
)
def test_register_creates_new_user_and_redirects_to_continue_page(
    client_request,
    mock_send_verify_code,
    mock_register_user,
    mock_get_user_by_email_not_found,
    mock_email_is_not_already_in_use,
    mock_send_verify_email,
    mock_login,
    phone_number_to_register_with,
    password,
):
    client_request.logout()
    user_data = {
        "name": "Some One Valid",
        "email_address": "notfound@example.gsa.gov",
        "mobile_number": phone_number_to_register_with,
        "password": password,
        "auth_type": "sms_auth",
    }

    page = client_request.post(
        "main.register",
        _data=user_data,
        _follow_redirects=True,
    )

    assert (
        page.select("main p")[0].text
        == "An email has been sent to notfound@example.gsa.gov."
    )

    mock_send_verify_email.assert_called_with(ANY, user_data["email_address"])
    mock_register_user.assert_called_with(
        user_data["name"],
        user_data["email_address"],
        user_data["mobile_number"],
        user_data["password"],
        user_data["auth_type"],
    )


def test_register_continue_handles_missing_session_sensibly(
    client_request,
):
    client_request.logout()
    # session is not set
    client_request.get(
        "main.registration_continue",
        _expected_redirect=url_for("main.show_accounts_or_dashboard"),
    )


def test_process_register_returns_200_when_mobile_number_is_invalid(
    client_request,
    mock_send_verify_code,
    mock_get_user_by_email_not_found,
    mock_login,
):
    client_request.logout()
    page = client_request.post(
        "main.register",
        _data={
            "name": "Bad Mobile",
            "email_address": "bad_mobile@example.gsa.gov",
            "mobile_number": "not good",
            "password": "validPassword!",
        },
        _expected_status=200,
    )

    assert "The string supplied did not seem to be a phone number" in page.text


def test_should_return_200_when_email_is_not_gov_uk(
    client_request,
    mock_get_organizations,
):
    client_request.logout()
    page = client_request.post(
        "main.register",
        _data={
            "name": "Firstname Lastname",
            "email_address": "bad_mobile@example.not.right",
            "mobile_number": "2020900123",
            "password": "validPassword!",
        },
        _expected_status=200,
    )

    assert (
        "Enter a public sector email address or find out who can use Notify"
        in normalize_spaces(page.select_one(".usa-error-message").text)
    )
    assert page.select_one(".usa-error-message a")["href"] == url_for("main.features")


@pytest.mark.parametrize(
    "email_address",
    [
        "notfound@example.gsa.gov",
        "example@lsquo.net",
    ],
)
def test_should_add_user_details_to_session(
    client_request,
    mocker,
    mock_send_verify_code,
    mock_register_user,
    mock_get_user_by_email_not_found,
    mock_get_organizations_with_unusual_domains,
    mock_email_is_not_already_in_use,
    mock_send_verify_email,
    mock_login,
    email_address,
):
    client_request.logout()
    client_request.post(
        "main.register",
        _data={
            "name": "Test Codes",
            "email_address": email_address,
            "mobile_number": "+12023123123",
            "password": "validPassword!",
        },
    )
    with client_request.session_transaction() as session:
        assert session["user_details"]["email"] == email_address


def test_should_return_200_if_password_is_on_list_of_commonly_used_passwords(
    client_request,
    mock_get_user_by_email,
    mock_login,
):
    client_request.logout()
    page = client_request.post(
        "main.register",
        _data={
            "name": "Bad Mobile",
            "email_address": "bad_mobile@example.gsa.gov",
            "mobile_number": "+12021234123",
            "password": "password",
        },
        _expected_status=200,
    )

    assert "Choose a password that’s harder to guess" in page.text


def test_register_with_existing_email_sends_emails(
    client_request,
    api_user_active,
    mock_get_user_by_email,
    mock_send_already_registered_email,
):
    client_request.logout()
    user_data = {
        "name": "Already Hasaccount",
        "email_address": api_user_active["email_address"],
        "mobile_number": "+12025900460",
        "password": "validPassword!",
    }

    client_request.post(
        "main.register",
        _data=user_data,
        _expected_redirect=url_for("main.registration_continue"),
    )


@pytest.mark.parametrize(
    ("email_address", "expected_value"),
    [
        ("first.last@example.com", "First Last"),
        ("first.middle.last@example.com", "First Middle Last"),
        ("first.m.last@example.com", "First Last"),
        ("first.last-last@example.com", "First Last-Last"),
        ("first.o'last@example.com", "First O’Last"),
        ("first.last+testing@example.com", "First Last"),
        ("first.last+testing+testing@example.com", "First Last"),
        ("first.last6@example.com", "First Last"),
        ("first.last.212@example.com", "First Last"),
        ("first.2.last@example.com", "First Last"),
        ("first.2b.last@example.com", "First Last"),
        ("first.1.2.3.last@example.com", "First Last"),
        ("first.last.1.2.3@example.com", "First Last"),
        # Instances where we can’t make a good-enough guess:
        ("example123@example.com", None),
        ("f.last@example.com", None),
        ("f.m.last@example.com", None),
    ],
)
def test_shows_name_on_registration_page_from_invite(
    client_request,
    fake_uuid,
    email_address,
    expected_value,
    sample_invite,
    mock_get_invited_user_by_id,
):
    sample_invite["email_address"] = email_address
    with client_request.session_transaction() as session:
        session["invited_user_id"] = sample_invite

    page = client_request.get("main.register_from_invite")
    assert page.select_one("input[name=name]").get("value") == expected_value


def test_shows_hidden_email_address_on_registration_page_from_invite(
    client_request,
    fake_uuid,
    sample_invite,
    mock_get_invited_user_by_id,
):
    with client_request.session_transaction() as session:
        session["invited_user_id"] = sample_invite

    page = client_request.get("main.register_from_invite")
    assert normalize_spaces(page.select_one("main p").text) == (
        "Your account will be created with this email address: invited_user@test.gsa.gov"
    )
    hidden_input = page.select_one("form .usa-sr-only input")
    for attr, value in (
        ("type", "email"),
        ("name", "username"),
        ("id", "username"),
        ("value", "invited_user@test.gsa.gov"),
        ("disabled", "disabled"),
        ("tabindex", "-1"),
        ("aria-hidden", "true"),
        ("autocomplete", "username"),
    ):
        assert hidden_input[attr] == value


@pytest.mark.parametrize(
    "extra_data",
    [
        {},
        # The username field is present in the page but the POST request
        # should ignore it
        {"username": "invited@user.com"},
        {"username": "anythingelse@example.com"},
    ],
)
def test_register_from_invite(
    client_request,
    fake_uuid,
    mock_email_is_not_already_in_use,
    mock_register_user,
    mock_send_verify_code,
    mock_accept_invite,
    mock_get_invited_user_by_id,
    sample_invite,
    extra_data,
):
    client_request.logout()
    with client_request.session_transaction() as session:
        session["invited_user_id"] = sample_invite["id"]
    client_request.post(
        "main.register_from_invite",
        _data=dict(
            name="Registered in another Browser",
            email_address=sample_invite["email_address"],
            mobile_number="+12024900460",
            service=sample_invite["service"],
            password="somreallyhardthingtoguess",
            auth_type="sms_auth",
            **extra_data
        ),
        _expected_redirect=url_for("main.verify"),
    )
    mock_register_user.assert_called_once_with(
        "Registered in another Browser",
        sample_invite["email_address"],
        "+12024900460",
        "somreallyhardthingtoguess",
        "sms_auth",
    )
    mock_get_invited_user_by_id.assert_called_once_with(sample_invite["id"])


def test_register_from_invite_when_user_registers_in_another_browser(
    client_request,
    api_user_active,
    mock_get_user_by_email,
    mock_accept_invite,
    mock_get_invited_user_by_id,
    sample_invite,
):
    client_request.logout()
    sample_invite["email_address"] = api_user_active["email_address"]
    with client_request.session_transaction() as session:
        session["invited_user_id"] = sample_invite["id"]
    client_request.post(
        "main.register_from_invite",
        _data={
            "name": "Registered in another Browser",
            "email_address": api_user_active["email_address"],
            "mobile_number": api_user_active["mobile_number"],
            "service": sample_invite["service"],
            "password": "somreallyhardthingtoguess",
            "auth_type": "sms_auth",
        },
        _expected_redirect=url_for("main.verify"),
    )


@pytest.mark.parametrize(
    "invite_email_address", ["gov-user@gsa.gov", "non-gov-user@example.com"]
)
def test_register_from_email_auth_invite(
    client_request,
    sample_invite,
    mock_email_is_not_already_in_use,
    mock_register_user,
    mock_get_user,
    mock_send_verify_email,
    mock_send_verify_code,
    mock_accept_invite,
    mock_create_event,
    mock_add_user_to_service,
    mock_get_service,
    mock_get_invited_user_by_id,
    invite_email_address,
    service_one,
    fake_uuid,
    mocker,
):
    client_request.logout()
    mock_login_user = mocker.patch("app.models.user.login_user")
    sample_invite["auth_type"] = "email_auth"
    sample_invite["email_address"] = invite_email_address
    with client_request.session_transaction() as session:
        session["invited_user_id"] = sample_invite["id"]
        # Prove that the user isn’t already signed in
        assert "user_id" not in session

    data = {
        "name": "invited user",
        "email_address": sample_invite["email_address"],
        "mobile_number": "2028675301",
        "password": "FSLKAJHFNvdzxgfyst",
        "service": sample_invite["service"],
        "auth_type": "email_auth",
    }

    client_request.post(
        "main.register_from_invite",
        _data=data,
        _expected_redirect=url_for(
            "main.service_dashboard",
            service_id=sample_invite["service"],
        ),
    )

    # doesn't send any 2fa code
    assert not mock_send_verify_email.called
    assert not mock_send_verify_code.called
    # creates user with email_auth set
    mock_register_user.assert_called_once_with(
        data["name"],
        data["email_address"],
        data["mobile_number"],
        data["password"],
        data["auth_type"],
    )
    # this is actually called twice, at the beginning of the function and then by the activate_user function
    mock_get_invited_user_by_id.assert_called_with(sample_invite["id"])
    mock_accept_invite.assert_called_once_with(
        sample_invite["service"], sample_invite["id"]
    )

    # just logs them in
    mock_login_user.assert_called_once_with(
        User(
            {
                "id": fake_uuid,  # This ID matches the return value of mock_register_user
                "platform_admin": False,
            }
        )
    )
    mock_add_user_to_service.assert_called_once_with(
        sample_invite["service"],
        fake_uuid,  # This ID matches the return value of mock_register_user
        {"manage_api_keys", "manage_service", "send_messages", "view_activity"},
        [],
    )

    with client_request.session_transaction() as session:
        # The user is signed in
        assert "user_id" in session
        # invited user details are still there so they can get added to the service
        assert session["invited_user_id"] == sample_invite["id"]


def test_can_register_email_auth_without_phone_number(
    client_request,
    sample_invite,
    mock_email_is_not_already_in_use,
    mock_register_user,
    mock_get_user,
    mock_send_verify_email,
    mock_send_verify_code,
    mock_accept_invite,
    mock_create_event,
    mock_add_user_to_service,
    mock_get_service,
    mock_get_invited_user_by_id,
):
    client_request.logout()
    sample_invite["auth_type"] = "email_auth"
    with client_request.session_transaction() as session:
        session["invited_user_id"] = sample_invite["id"]

    data = {
        "name": "invited user",
        "email_address": sample_invite["email_address"],
        "mobile_number": "",
        "password": "FSLKAJHFNvdzxgfyst",
        "service": sample_invite["service"],
        "auth_type": "email_auth",
    }

    client_request.post(
        "main.register_from_invite",
        _data=data,
        _expected_redirect=url_for(
            "main.service_dashboard",
            service_id=sample_invite["service"],
        ),
    )

    mock_register_user.assert_called_once_with(
        ANY, ANY, None, ANY, ANY  # mobile_number
    )


def test_cannot_register_with_sms_auth_and_missing_mobile_number(
    client_request,
    mock_send_verify_code,
    mock_get_user_by_email_not_found,
    mock_login,
):
    client_request.logout()
    page = client_request.post(
        "main.register",
        _data={
            "name": "Missing Mobile",
            "email_address": "missing_mobile@example.gsa.gov",
            "password": "validPassword!",
        },
        _expected_status=200,
    )

    err = page.select_one(".usa-error-message")
    assert err.text.strip() == "Error: Cannot be empty"
    assert err.attrs["data-error-label"] == "mobile_number"


def test_register_from_invite_form_doesnt_show_mobile_number_field_if_email_auth(
    client_request,
    sample_invite,
    mock_get_invited_user_by_id,
):
    client_request.logout()
    sample_invite["auth_type"] = "email_auth"
    with client_request.session_transaction() as session:
        session["invited_user_id"] = sample_invite["id"]

    page = client_request.get("main.register_from_invite")

    assert (
        page.find("input", attrs={"name": "auth_type"}).attrs["value"] == "email_auth"
    )
    assert page.find("input", attrs={"name": "mobile_number"}) is None
