"""Tests for role-related repository: get_user_role, set_user_role, is_admin, can_access_miniapp."""

from unittest.mock import patch

import pytest
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker

from duty_teller.db.models import Base, Role
from duty_teller.db.repository import (
    ROLE_USER,
    ROLE_ADMIN,
    get_user_by_username,
    get_user_role,
    set_user_role,
    is_admin_for_telegram_user,
    can_access_miniapp_for_telegram_user,
    can_access_miniapp_for_user,
    get_or_create_user,
)


@pytest.fixture
def session():
    """Session with roles and users tables; roles 'user' and 'admin' inserted."""
    engine = create_engine(
        "sqlite:///:memory:", connect_args={"check_same_thread": False}
    )
    Base.metadata.create_all(engine)
    Session = sessionmaker(bind=engine, autocommit=False, autoflush=False)
    s = Session()
    try:
        for name in (ROLE_USER, ROLE_ADMIN):
            r = Role(name=name)
            s.add(r)
        s.commit()
        yield s
    finally:
        s.close()
        engine.dispose()


@pytest.fixture
def user_no_role(session):
    """User with telegram_user_id set, no role."""
    u = get_or_create_user(
        session,
        telegram_user_id=100,
        full_name="No Role",
        username="norole",
    )
    return u


@pytest.fixture
def role_user(session):
    return session.query(Role).filter(Role.name == ROLE_USER).first()


@pytest.fixture
def role_admin(session):
    return session.query(Role).filter(Role.name == ROLE_ADMIN).first()


def test_get_user_role_none_when_no_role(session, user_no_role):
    assert get_user_role(session, user_no_role.id) is None


def test_get_user_role_returns_name(session, user_no_role, role_user, role_admin):
    user_no_role.role_id = role_user.id
    session.commit()
    assert get_user_role(session, user_no_role.id) == ROLE_USER
    user_no_role.role_id = role_admin.id
    session.commit()
    assert get_user_role(session, user_no_role.id) == ROLE_ADMIN


def test_set_user_role(session, user_no_role, role_user):
    updated = set_user_role(session, user_no_role.id, ROLE_USER)
    assert updated is not None
    assert updated.id == user_no_role.id
    assert updated.role_id == role_user.id
    session.refresh(user_no_role)
    assert user_no_role.role.name == ROLE_USER


def test_set_user_role_invalid_name_returns_none(session, user_no_role):
    assert set_user_role(session, user_no_role.id, "superuser") is None
    assert set_user_role(session, 99999, ROLE_USER) is None


@patch("duty_teller.db.repository.config.is_admin")
@patch("duty_teller.db.repository.config.is_admin_by_phone")
def test_is_admin_for_telegram_user_from_db_role(
    mock_admin_phone, mock_admin, session, user_no_role, role_admin, role_user
):
    mock_admin.return_value = False
    mock_admin_phone.return_value = False
    user_no_role.role_id = role_user.id
    session.commit()
    assert is_admin_for_telegram_user(session, 100) is False
    user_no_role.role_id = role_admin.id
    session.commit()
    assert is_admin_for_telegram_user(session, 100) is True
    mock_admin.assert_not_called()
    mock_admin_phone.assert_not_called()


@patch("duty_teller.db.repository.config.is_admin")
@patch("duty_teller.db.repository.config.is_admin_by_phone")
def test_is_admin_for_telegram_user_fallback_when_no_role(
    mock_admin_phone, mock_admin, session, user_no_role
):
    mock_admin.return_value = True
    mock_admin_phone.return_value = False
    assert is_admin_for_telegram_user(session, 100) is True
    mock_admin.assert_called_once()
    mock_admin.return_value = False
    mock_admin_phone.return_value = True
    assert is_admin_for_telegram_user(session, 100) is True


@patch("duty_teller.db.repository.config.is_admin")
@patch("duty_teller.db.repository.config.is_admin_by_phone")
def test_is_admin_for_telegram_user_false_when_no_user(
    mock_admin_phone, mock_admin, session
):
    mock_admin.return_value = False
    mock_admin_phone.return_value = False
    assert is_admin_for_telegram_user(session, 99999) is False


def test_can_access_miniapp_for_telegram_user_no_user(session):
    assert can_access_miniapp_for_telegram_user(session, 99999) is False


def test_can_access_miniapp_for_telegram_user_with_role(
    session, user_no_role, role_user, role_admin
):
    user_no_role.role_id = role_user.id
    session.commit()
    assert can_access_miniapp_for_telegram_user(session, 100) is True
    user_no_role.role_id = role_admin.id
    session.commit()
    assert can_access_miniapp_for_telegram_user(session, 100) is True


@patch("duty_teller.db.repository.config.is_admin")
@patch("duty_teller.db.repository.config.is_admin_by_phone")
def test_can_access_miniapp_for_telegram_user_fallback_when_no_role(
    mock_admin_phone, mock_admin, session, user_no_role
):
    mock_admin.return_value = False
    mock_admin_phone.return_value = False
    assert can_access_miniapp_for_telegram_user(session, 100) is False
    mock_admin.return_value = True
    assert can_access_miniapp_for_telegram_user(session, 100) is True


def test_can_access_miniapp_for_user_none(session):
    assert can_access_miniapp_for_user(session, None) is False


def test_get_user_by_username(session, user_no_role):
    u = get_user_by_username(session, "norole")
    assert u is not None and u.id == user_no_role.id
    u2 = get_user_by_username(session, "@norole")
    assert u2 is not None and u2.id == user_no_role.id
    u3 = get_user_by_username(session, "NOROLE")
    assert u3 is not None and u3.id == user_no_role.id
    assert get_user_by_username(session, "unknown") is None
    assert get_user_by_username(session, "") is None
