import pytest

from tests.factories import (
    ApplicationRoleFactory,
    TaskOrderFactory,
    UserFactory,
    PortfolioFactory,
    PortfolioRoleFactory,
)
from atst.domain.authz import Authorization, user_can_access
from atst.domain.authz.decorator import user_can_access_decorator
from atst.domain.permission_sets import PermissionSets
from atst.domain.exceptions import UnauthorizedError
from atst.models.permissions import Permissions
from atst.domain.portfolio_roles import PortfolioRoles

from tests.utils import FakeLogger


@pytest.fixture
def invalid_user():
    return UserFactory.create()


@pytest.fixture
def task_order():
    return TaskOrderFactory.create()


def test_has_portfolio_permission():
    role_one = PermissionSets.get(PermissionSets.VIEW_PORTFOLIO_FUNDING)
    role_two = PermissionSets.get(PermissionSets.VIEW_PORTFOLIO_REPORTS)
    port_role = PortfolioRoleFactory.create(permission_sets=[role_one, role_two])
    different_user = UserFactory.create()
    assert Authorization.has_portfolio_permission(
        port_role.user, port_role.portfolio, Permissions.VIEW_PORTFOLIO_REPORTS
    )
    assert not Authorization.has_portfolio_permission(
        port_role.user, port_role.portfolio, Permissions.CREATE_TASK_ORDER
    )
    assert not Authorization.has_portfolio_permission(
        different_user, port_role.portfolio, Permissions.VIEW_PORTFOLIO_REPORTS
    )


def test_has_application_permission():
    role_one = PermissionSets.get(PermissionSets.EDIT_APPLICATION_TEAM)
    role_two = PermissionSets.get(PermissionSets.EDIT_APPLICATION_ENVIRONMENTS)
    app_role = ApplicationRoleFactory.create(permission_sets=[role_one, role_two])
    different_user = UserFactory.create()
    assert Authorization.has_application_permission(
        app_role.user, app_role.application, Permissions.EDIT_ENVIRONMENT
    )
    assert not Authorization.has_portfolio_permission(
        app_role.user, app_role.application, Permissions.DELETE_ENVIRONMENT
    )
    assert not Authorization.has_portfolio_permission(
        different_user, app_role.application, Permissions.DELETE_ENVIRONMENT
    )


def test_user_can_access():
    ccpo = UserFactory.create_ccpo()
    edit_admin = UserFactory.create()
    view_admin = UserFactory.create()

    portfolio = PortfolioFactory.create(owner=edit_admin)
    # factory gives view perms by default
    view_admin_pr = PortfolioRoleFactory.create(user=view_admin, portfolio=portfolio)

    # check a site-wide permission
    assert user_can_access(ccpo, Permissions.VIEW_AUDIT_LOG)

    with pytest.raises(UnauthorizedError):
        user_can_access(edit_admin, Permissions.VIEW_AUDIT_LOG)

    with pytest.raises(UnauthorizedError):
        user_can_access(view_admin, Permissions.VIEW_AUDIT_LOG)

    # check a portfolio view permission
    assert user_can_access(ccpo, Permissions.VIEW_PORTFOLIO, portfolio=portfolio)
    assert user_can_access(edit_admin, Permissions.VIEW_PORTFOLIO, portfolio=portfolio)
    assert user_can_access(view_admin, Permissions.VIEW_PORTFOLIO, portfolio=portfolio)

    # check a portfolio edit permission
    assert user_can_access(ccpo, Permissions.EDIT_PORTFOLIO_NAME, portfolio=portfolio)
    assert user_can_access(
        edit_admin, Permissions.EDIT_PORTFOLIO_NAME, portfolio=portfolio
    )
    with pytest.raises(UnauthorizedError):
        user_can_access(
            view_admin, Permissions.EDIT_PORTFOLIO_NAME, portfolio=portfolio
        )

    # check when portfolio_role is disabled
    PortfolioRoles.disable(portfolio_role=view_admin_pr)
    with pytest.raises(UnauthorizedError):
        user_can_access(
            view_admin, Permissions.EDIT_PORTFOLIO_NAME, portfolio=portfolio
        )


@pytest.fixture
def set_current_user(request_ctx):
    def _set_current_user(user):
        request_ctx.g.current_user = user

    yield _set_current_user

    request_ctx.g.current_user = None


def test_user_can_access_decorator_atat_level(set_current_user):
    ccpo = UserFactory.create_ccpo()
    rando = UserFactory.create()

    @user_can_access_decorator(Permissions.VIEW_AUDIT_LOG)
    def _access_activity_log(*args, **kwargs):
        return True

    set_current_user(ccpo)
    assert _access_activity_log()

    set_current_user(rando)
    with pytest.raises(UnauthorizedError):
        _access_activity_log()


def test_user_can_access_decorator_portfolio_level(set_current_user, request_ctx):
    ccpo = UserFactory.create_ccpo()
    edit_admin = UserFactory.create()
    view_admin = UserFactory.create()

    portfolio = PortfolioFactory.create(owner=edit_admin)
    # factory gives view perms by default
    PortfolioRoleFactory.create(user=view_admin, portfolio=portfolio)

    request_ctx.g.portfolio = portfolio
    request_ctx.g.application = None

    @user_can_access_decorator(Permissions.EDIT_PORTFOLIO_NAME)
    def _edit_portfolio_name(*args, **kwargs):
        return True

    set_current_user(ccpo)
    assert _edit_portfolio_name(portfolio_id=portfolio.id)

    set_current_user(edit_admin)
    assert _edit_portfolio_name(portfolio_id=portfolio.id)

    set_current_user(view_admin)
    with pytest.raises(UnauthorizedError):
        _edit_portfolio_name(portfolio_id=portfolio.id)


def test_user_can_access_decorator_application_level(set_current_user, request_ctx):
    ccpo = UserFactory.create_ccpo()
    port_admin = UserFactory.create()
    app_user = UserFactory.create()
    rando = UserFactory.create()

    portfolio = PortfolioFactory.create(
        owner=port_admin, applications=[{"name": "Mos Eisley"}]
    )
    app = portfolio.applications[0]
    ApplicationRoleFactory.create(application=app, user=app_user)

    request_ctx.g.portfolio = portfolio
    request_ctx.g.application = app

    @user_can_access_decorator(Permissions.VIEW_APPLICATION)
    def _stroll_into_mos_eisley(*args, **kwargs):
        return True

    set_current_user(ccpo)
    assert _stroll_into_mos_eisley(application_id=app.id)

    set_current_user(port_admin)
    assert _stroll_into_mos_eisley(application_id=app.id)

    set_current_user(app_user)
    assert _stroll_into_mos_eisley(application_id=app.id)

    set_current_user(rando)
    with pytest.raises(UnauthorizedError):
        _stroll_into_mos_eisley(application_id=app.id)


def test_user_can_access_decorator_override(set_current_user):
    rando_calrissian = UserFactory.create()
    darth_vader = UserFactory.create()

    def _can_fly_the_millenium_falcon(u, *args, **kwargs):
        if u == rando_calrissian:
            return True
        else:
            raise UnauthorizedError(u, "is not rando")

    @user_can_access_decorator(
        Permissions.EDIT_PORTFOLIO_NAME, override=_can_fly_the_millenium_falcon
    )
    def _cloud_city(*args, **kwargs):
        return True

    set_current_user(rando_calrissian)
    assert _cloud_city()

    set_current_user(darth_vader)
    with pytest.raises(UnauthorizedError):
        assert _cloud_city()


def test_user_can_access_decorator_logs_access(
    set_current_user, monkeypatch, mock_logger
):
    user = UserFactory.create()  # this emits an 'Audit Event insert' event

    @user_can_access_decorator(Permissions.EDIT_PORTFOLIO_NAME)
    def _do_something(*args, **kwargs):
        return True

    set_current_user(user)

    monkeypatch.setattr(
        "atst.domain.authz.decorator.check_access", lambda *a, **k: True
    )
    num_msgs = len(mock_logger.messages)
    _do_something()
    assert len(mock_logger.messages) == num_msgs + 1
    assert "accessed" in mock_logger.messages[-1]
    assert "GET" in mock_logger.messages[-1]

    def _unauthorized(*a, **k):
        raise UnauthorizedError(user, "do something")

    monkeypatch.setattr("atst.domain.authz.decorator.check_access", _unauthorized)
    num_msgs = len(mock_logger.messages)
    with pytest.raises(UnauthorizedError):
        _do_something()

    assert len(mock_logger.messages) == num_msgs + 1
    assert "denied access" in mock_logger.messages[-1]
    assert "GET" in mock_logger.messages[-1]
