from __future__ import annotations

import pytest

from models import MiningOperationType, Project, Scenario, ScenarioStatus, User
from services.authorization import (
    ensure_project_access,
    ensure_scenario_access,
    ensure_scenario_in_project,
)
from services.exceptions import AuthorizationError, EntityNotFoundError
from services.unit_of_work import UnitOfWork


def _create_user_with_roles(
    uow: UnitOfWork,
    *,
    email: str,
    username: str,
    roles: set[str],
) -> User:
    assert uow.users is not None
    assert uow.roles is not None

    user = User(
        email=email,
        username=username,
        password_hash=User.hash_password("secret"),
        is_active=True,
    )
    uow.users.create(user)
    uow.ensure_default_roles()

    for role_name in roles:
        role = uow.roles.get_by_name(role_name)
        assert role is not None, f"Role {role_name} should exist"
        uow.users.assign_role(user_id=user.id, role_id=role.id)

    return uow.users.get(user.id, with_roles=True)


def _create_project(uow: UnitOfWork, name: str) -> Project:
    assert uow.projects is not None
    project = Project(
        name=name,
        location=None,
        operation_type=MiningOperationType.OTHER,
        description=None,
    )
    uow.projects.create(project)
    return project


def _create_scenario(uow: UnitOfWork, project: Project, name: str) -> Scenario:
    assert uow.scenarios is not None
    scenario = Scenario(
        project_id=project.id,
        name=name,
        description=None,
        status=ScenarioStatus.DRAFT,
    )
    uow.scenarios.create(scenario)
    return scenario


def test_ensure_project_access_allows_view_roles(unit_of_work_factory) -> None:
    with unit_of_work_factory() as uow:
        project = _create_project(uow, "Project A")
        user = _create_user_with_roles(
            uow,
            email="viewer@example.com",
            username="viewer",
            roles={"viewer"},
        )

        resolved = ensure_project_access(
            uow,
            project_id=project.id,
            user=user,
        )
        assert resolved.id == project.id

        with pytest.raises(AuthorizationError):
            ensure_project_access(
                uow,
                project_id=project.id,
                user=user,
                require_manage=True,
            )


def test_ensure_project_access_allows_manage_roles(unit_of_work_factory) -> None:
    with unit_of_work_factory() as uow:
        project = _create_project(uow, "Project B")
        user = _create_user_with_roles(
            uow,
            email="manager@example.com",
            username="manager",
            roles={"project_manager"},
        )

        resolved = ensure_project_access(
            uow,
            project_id=project.id,
            user=user,
            require_manage=True,
        )
        assert resolved.id == project.id


def test_ensure_scenario_access(unit_of_work_factory) -> None:
    with unit_of_work_factory() as uow:
        project = _create_project(uow, "Project C")
        scenario = _create_scenario(uow, project, "Scenario C1")
        user = _create_user_with_roles(
            uow,
            email="analyst@example.com",
            username="analyst",
            roles={"analyst"},
        )

        resolved = ensure_scenario_access(
            uow,
            scenario_id=scenario.id,
            user=user,
        )
        assert resolved.id == scenario.id

        with pytest.raises(AuthorizationError):
            ensure_scenario_access(
                uow,
                scenario_id=scenario.id,
                user=user,
                require_manage=True,
            )


def test_ensure_scenario_in_project_validates_membership(unit_of_work_factory) -> None:
    with unit_of_work_factory() as uow:
        project_one = _create_project(uow, "Project D")
        project_two = _create_project(uow, "Project E")
        scenario = _create_scenario(uow, project_one, "Scenario D1")
        user = _create_user_with_roles(
            uow,
            email="manager2@example.com",
            username="manager2",
            roles={"project_manager"},
        )

        resolved = ensure_scenario_in_project(
            uow,
            project_id=project_one.id,
            scenario_id=scenario.id,
            user=user,
            require_manage=True,
        )
        assert resolved.id == scenario.id

        with pytest.raises(EntityNotFoundError):
            ensure_scenario_in_project(
                uow,
                project_id=project_two.id,
                scenario_id=scenario.id,
                user=user,
            )
