from __future__ import annotations

from datetime import UTC, datetime
from unittest.mock import AsyncMock, MagicMock

import pytest
from fastapi import HTTPException

from app.auth.better_auth import CurrentUser
from app.auth.tenant import get_current_restaurant
from app.models.auth_mirror import Team, TeamMember
from app.models.restaurant import Restaurant


def _scalar_result(value: object) -> MagicMock:
    result = MagicMock()
    result.scalar_one_or_none.return_value = value
    return result


def _team(*, team_id: str = "team-1", organization_id: str = "org-1") -> Team:
    now = datetime.now(UTC)
    return Team(
        id=team_id,
        name="Main Team",
        organization_id=organization_id,
        created_at=now,
        updated_at=now,
    )


def _team_member(*, team_id: str = "team-1", user_id: str = "user-1") -> TeamMember:
    return TeamMember(
        id="tm-1",
        team_id=team_id,
        user_id=user_id,
        created_at=datetime.now(UTC),
    )


def _restaurant(*, team_id: str = "team-1") -> Restaurant:
    return Restaurant(
        id="rest-1",
        name="Main Restaurant",
        slug="main-restaurant",
        timezone="Europe/Brussels",
        team_id=team_id,
        phone=None,
        settings=None,
    )


def _user(
    *,
    user_id: str = "user-1",
    role: str | None = "member",
    active_org_id: str | None = "org-1",
    active_team_id: str | None = "team-1",
    active_restaurant_id: str | None = "rest-1",
) -> CurrentUser:
    return CurrentUser(
        id=user_id,
        email="user@example.com",
        email_verified=True,
        active_org_id=active_org_id,
        active_team_id=active_team_id,
        active_restaurant_id=active_restaurant_id,
        role=role,
    )


@pytest.mark.asyncio
async def test_admin_with_team_in_active_org_gets_restaurant() -> None:
    session = AsyncMock()
    restaurant = _restaurant()
    session.execute.side_effect = [
        _scalar_result(_team()),
        _scalar_result(restaurant),
    ]

    resolved = await get_current_restaurant(
        current_user=_user(role="admin"),
        session=session,
    )

    assert resolved is restaurant
    assert session.execute.await_count == 2


@pytest.mark.asyncio
async def test_admin_cross_org_tampering_is_forbidden() -> None:
    session = AsyncMock()
    session.execute.return_value = _scalar_result(_team(organization_id="org-2"))

    with pytest.raises(HTTPException) as exc_info:
        await get_current_restaurant(
            current_user=_user(role="owner", active_org_id="org-1"),
            session=session,
        )

    assert exc_info.value.status_code == 403
    assert exc_info.value.detail == "Active team does not belong to active organization."
    assert session.execute.await_count == 1


@pytest.mark.asyncio
async def test_member_without_team_membership_is_forbidden() -> None:
    session = AsyncMock()
    session.execute.side_effect = [
        _scalar_result(_team()),
        _scalar_result(None),
    ]

    with pytest.raises(HTTPException) as exc_info:
        await get_current_restaurant(
            current_user=_user(role="member"),
            session=session,
        )

    assert exc_info.value.status_code == 403
    assert exc_info.value.detail == "User is not a member of the active restaurant's team."
    assert session.execute.await_count == 2


@pytest.mark.asyncio
async def test_member_with_team_membership_gets_restaurant() -> None:
    session = AsyncMock()
    restaurant = _restaurant()
    session.execute.side_effect = [
        _scalar_result(_team()),
        _scalar_result(_team_member()),
        _scalar_result(restaurant),
    ]

    resolved = await get_current_restaurant(
        current_user=_user(role="member"),
        session=session,
    )

    assert resolved is restaurant
    assert session.execute.await_count == 3


@pytest.mark.asyncio
async def test_missing_active_team_id_is_forbidden() -> None:
    session = AsyncMock()

    with pytest.raises(HTTPException) as exc_info:
        await get_current_restaurant(
            current_user=_user(active_team_id=None),
            session=session,
        )

    assert exc_info.value.status_code == 403
    assert exc_info.value.detail == (
        "No active restaurant selected. Complete onboarding or switch restaurants."
    )
    session.execute.assert_not_awaited()
