"""Tests for admin API: GET /api/admin/me, GET /api/admin/users, PATCH /api/admin/duties/:id."""

from unittest.mock import ANY, MagicMock, patch

import pytest
from fastapi.testclient import TestClient

from duty_teller.api.app import app


@pytest.fixture
def client():
    return TestClient(app)


# --- GET /api/admin/me ---


@patch("duty_teller.api.dependencies.config.MINI_APP_SKIP_AUTH", True)
def test_admin_me_skip_auth_returns_is_admin_false(client):
    """With MINI_APP_SKIP_AUTH, GET /api/admin/me returns is_admin: false (no real user)."""
    r = client.get("/api/admin/me")
    assert r.status_code == 200
    assert r.json() == {"is_admin": False}


@patch("duty_teller.api.app.is_admin_for_telegram_user")
@patch("duty_teller.api.dependencies.can_access_miniapp_for_telegram_user")
@patch("duty_teller.api.dependencies.get_user_by_telegram_id")
@patch("duty_teller.api.dependencies.validate_init_data_with_reason")
@patch("duty_teller.api.dependencies.config.MINI_APP_SKIP_AUTH", False)
def test_admin_me_returns_is_admin_true_when_admin(
    mock_validate, mock_get_user, mock_can_access, mock_is_admin, client
):
    """When user is admin, GET /api/admin/me returns is_admin: true."""
    from types import SimpleNamespace

    mock_validate.return_value = (100, "user", "ok", "en")
    mock_get_user.return_value = SimpleNamespace(full_name="Admin", username="admin")
    mock_can_access.return_value = True
    mock_is_admin.return_value = True
    r = client.get("/api/admin/me", headers={"X-Telegram-Init-Data": "auth_date=1&user=%7B%22id%22%3A100%7D&hash=x"})
    assert r.status_code == 200
    assert r.json() == {"is_admin": True}


@patch("duty_teller.api.app.is_admin_for_telegram_user")
@patch("duty_teller.api.dependencies.can_access_miniapp_for_telegram_user")
@patch("duty_teller.api.dependencies.get_user_by_telegram_id")
@patch("duty_teller.api.dependencies.validate_init_data_with_reason")
@patch("duty_teller.api.dependencies.config.MINI_APP_SKIP_AUTH", False)
def test_admin_me_returns_is_admin_false_when_not_admin(
    mock_validate, mock_get_user, mock_can_access, mock_is_admin, client
):
    """When user is not admin, GET /api/admin/me returns is_admin: false."""
    from types import SimpleNamespace

    mock_validate.return_value = (200, "user", "ok", "en")
    mock_get_user.return_value = SimpleNamespace(full_name="User", username="user")
    mock_can_access.return_value = True
    mock_is_admin.return_value = False
    r = client.get("/api/admin/me", headers={"X-Telegram-Init-Data": "auth_date=1&user=%7B%22id%22%3A200%7D&hash=x"})
    assert r.status_code == 200
    assert r.json() == {"is_admin": False}


# --- GET /api/admin/users ---


@patch("duty_teller.api.dependencies.config.MINI_APP_SKIP_AUTH", False)
def test_admin_users_403_without_init_data(client):
    """GET /api/admin/users without initData returns 403."""
    r = client.get("/api/admin/users")
    assert r.status_code == 403


@patch("duty_teller.api.dependencies.is_admin_for_telegram_user")
@patch("duty_teller.api.dependencies.can_access_miniapp_for_telegram_user")
@patch("duty_teller.api.dependencies.get_user_by_telegram_id")
@patch("duty_teller.api.dependencies.validate_init_data_with_reason")
@patch("duty_teller.api.dependencies.config.MINI_APP_SKIP_AUTH", False)
def test_admin_users_403_when_not_admin(
    mock_validate, mock_get_user, mock_can_access, mock_is_admin, client
):
    """GET /api/admin/users when not admin returns 403 with admin_only message."""
    from types import SimpleNamespace

    mock_validate.return_value = (100, "u", "ok", "en")
    mock_get_user.return_value = SimpleNamespace(full_name="U", username="u")
    mock_can_access.return_value = True
    mock_is_admin.return_value = False  # not admin
    r = client.get(
        "/api/admin/users",
        headers={"X-Telegram-Init-Data": "auth_date=1&user=%7B%22id%22%3A100%7D&hash=x"},
    )
    assert r.status_code == 403
    detail = r.json()["detail"]
    assert "admin" in detail.lower() or "администратор" in detail or "only" in detail


@patch("duty_teller.api.app.get_users_for_admin")
@patch("duty_teller.api.dependencies.is_admin_for_telegram_user")
@patch("duty_teller.api.dependencies.can_access_miniapp_for_telegram_user")
@patch("duty_teller.api.dependencies.get_user_by_telegram_id")
@patch("duty_teller.api.dependencies.validate_init_data_with_reason")
@patch("duty_teller.api.dependencies.config.MINI_APP_SKIP_AUTH", False)
def test_admin_users_200_returns_list(
    mock_validate, mock_get_user, mock_can_access, mock_is_admin, mock_get_users, client
):
    """GET /api/admin/users returns list of id, full_name, username, role_id."""
    from types import SimpleNamespace

    mock_validate.return_value = (1, "admin", "ok", "en")
    mock_get_user.return_value = SimpleNamespace(full_name="Admin", username="admin")
    mock_can_access.return_value = True
    mock_is_admin.return_value = True
    mock_get_users.return_value = [
        SimpleNamespace(id=1, full_name="Alice", username="alice", role_id=1),
        SimpleNamespace(id=2, full_name="Bob", username=None, role_id=2),
    ]
    r = client.get(
        "/api/admin/users",
        headers={"X-Telegram-Init-Data": "auth_date=1&user=%7B%22id%22%3A1%7D&hash=x"},
    )
    assert r.status_code == 200
    data = r.json()
    assert len(data) == 2
    assert data[0]["id"] == 1
    assert data[0]["full_name"] == "Alice"
    assert data[0]["username"] == "alice"
    assert data[0]["role_id"] == 1
    assert data[1]["id"] == 2
    assert data[1]["full_name"] == "Bob"
    assert data[1]["username"] is None
    assert data[1]["role_id"] == 2


# --- PATCH /api/admin/duties/:id ---


@patch("duty_teller.api.dependencies.config.MINI_APP_SKIP_AUTH", False)
def test_admin_reassign_403_without_auth(client):
    """PATCH /api/admin/duties/1 without auth returns 403."""
    r = client.patch(
        "/api/admin/duties/1",
        json={"user_id": 2},
    )
    assert r.status_code == 403


@patch("duty_teller.api.app.require_admin_telegram_id")
@patch("duty_teller.api.dependencies.config.MINI_APP_SKIP_AUTH", False)
def test_admin_reassign_403_when_not_admin(mock_require_admin, client):
    """PATCH /api/admin/duties/1 when not admin returns 403."""
    from fastapi import HTTPException

    from duty_teller.i18n import t

    mock_require_admin.side_effect = HTTPException(
        status_code=403, detail=t("en", "import.admin_only")
    )
    r = client.patch(
        "/api/admin/duties/1",
        json={"user_id": 2},
        headers={"X-Telegram-Init-Data": "x"},
    )
    assert r.status_code == 403


@patch("duty_teller.api.app.invalidate_duty_related_caches")
@patch("duty_teller.api.app.update_duty_user")
@patch("duty_teller.api.app.get_duty_by_id")
@patch("duty_teller.api.dependencies.is_admin_for_telegram_user")
@patch("duty_teller.api.dependencies.can_access_miniapp_for_telegram_user")
@patch("duty_teller.api.dependencies.get_user_by_telegram_id")
@patch("duty_teller.api.dependencies.validate_init_data_with_reason")
@patch("duty_teller.api.dependencies.config.MINI_APP_SKIP_AUTH", False)
def test_admin_reassign_404_when_duty_missing(
    mock_validate,
    mock_get_user,
    mock_can_access,
    mock_is_admin,
    mock_get_duty,
    mock_update,
    mock_invalidate,
    client,
):
    """PATCH /api/admin/duties/999 returns 404 when duty not found."""
    from types import SimpleNamespace

    mock_validate.return_value = (1, "admin", "ok", "en")
    mock_get_user.return_value = SimpleNamespace(full_name="Admin", username="admin")
    mock_can_access.return_value = True
    mock_is_admin.return_value = True
    mock_get_duty.return_value = None
    r = client.patch(
        "/api/admin/duties/999",
        json={"user_id": 2},
        headers={"X-Telegram-Init-Data": "auth_date=1&user=%7B%22id%22%3A1%7D&hash=x"},
    )
    assert r.status_code == 404
    mock_update.assert_not_called()
    mock_invalidate.assert_not_called()


@patch("duty_teller.api.app.invalidate_duty_related_caches")
@patch("duty_teller.api.app.update_duty_user")
@patch("duty_teller.api.app.get_duty_by_id")
@patch("duty_teller.api.dependencies.is_admin_for_telegram_user")
@patch("duty_teller.api.dependencies.can_access_miniapp_for_telegram_user")
@patch("duty_teller.api.dependencies.get_user_by_telegram_id")
@patch("duty_teller.api.dependencies.validate_init_data_with_reason")
@patch("duty_teller.api.dependencies.config.MINI_APP_SKIP_AUTH", False)
def test_admin_reassign_400_when_user_not_found(
    mock_validate,
    mock_get_user,
    mock_can_access,
    mock_is_admin,
    mock_get_duty,
    mock_update,
    mock_invalidate,
    client,
):
    """PATCH /api/admin/duties/1 returns 400 when user_id does not exist."""
    from types import SimpleNamespace

    mock_validate.return_value = (1, "admin", "ok", "en")
    mock_get_user.return_value = SimpleNamespace(full_name="Admin", username="admin")
    mock_can_access.return_value = True
    mock_is_admin.return_value = True
    mock_get_duty.return_value = SimpleNamespace(
        id=1, user_id=10, start_at="2026-01-15T09:00:00Z", end_at="2026-01-15T18:00:00Z"
    )
    mock_session = MagicMock()
    mock_session.get.return_value = None  # User not found
    with patch("duty_teller.api.app.get_db_session") as mock_db:
        mock_db.return_value.__enter__.return_value = mock_session
        mock_db.return_value.__exit__.return_value = None
        r = client.patch(
            "/api/admin/duties/1",
            json={"user_id": 999},
            headers={"X-Telegram-Init-Data": "auth_date=1&user=%7B%22id%22%3A1%7D&hash=x"},
        )
    assert r.status_code == 400
    mock_update.assert_not_called()
    mock_invalidate.assert_not_called()


@patch("duty_teller.api.app.invalidate_duty_related_caches")
@patch("duty_teller.api.app.update_duty_user")
@patch("duty_teller.api.app.get_duty_by_id")
@patch("duty_teller.api.dependencies.is_admin_for_telegram_user")
@patch("duty_teller.api.dependencies.can_access_miniapp_for_telegram_user")
@patch("duty_teller.api.dependencies.get_user_by_telegram_id")
@patch("duty_teller.api.dependencies.validate_init_data_with_reason")
@patch("duty_teller.api.dependencies.config.MINI_APP_SKIP_AUTH", False)
def test_admin_reassign_200_updates_and_invalidates(
    mock_validate,
    mock_get_user,
    mock_can_access,
    mock_is_admin,
    mock_get_duty,
    mock_update_duty_user,
    mock_invalidate,
    client,
):
    """PATCH /api/admin/duties/1 with valid body returns 200 and invalidates caches."""
    from types import SimpleNamespace

    mock_validate.return_value = (1, "admin", "ok", "en")
    mock_get_user.return_value = SimpleNamespace(full_name="Admin", username="admin")
    mock_can_access.return_value = True
    mock_is_admin.return_value = True
    duty = SimpleNamespace(
        id=1,
        user_id=10,
        start_at="2026-01-15T09:00:00Z",
        end_at="2026-01-15T18:00:00Z",
    )
    updated_duty = SimpleNamespace(
        id=1,
        user_id=2,
        start_at="2026-01-15T09:00:00Z",
        end_at="2026-01-15T18:00:00Z",
    )
    mock_get_duty.return_value = duty
    mock_update_duty_user.return_value = updated_duty
    mock_session = MagicMock()
    mock_session.get.return_value = SimpleNamespace(id=2)  # User exists
    with patch("duty_teller.api.app.get_db_session") as mock_db:
        mock_db.return_value.__enter__.return_value = mock_session
        mock_db.return_value.__exit__.return_value = None
        r = client.patch(
            "/api/admin/duties/1",
            json={"user_id": 2},
            headers={"X-Telegram-Init-Data": "auth_date=1&user=%7B%22id%22%3A1%7D&hash=x"},
        )
    assert r.status_code == 200
    data = r.json()
    assert data["id"] == 1
    assert data["user_id"] == 2
    assert data["start_at"] == "2026-01-15T09:00:00Z"
    assert data["end_at"] == "2026-01-15T18:00:00Z"
    mock_update_duty_user.assert_called_once_with(ANY, 1, 2, commit=True)
    mock_invalidate.assert_called_once()


@patch("duty_teller.api.dependencies.config.MINI_APP_SKIP_AUTH", True)
def test_admin_users_403_when_skip_auth(client):
    """GET /api/admin/users with MINI_APP_SKIP_AUTH returns 403 (admin routes disabled)."""
    r = client.get("/api/admin/users")
    assert r.status_code == 403
    detail = r.json()["detail"]
    assert "admin" in detail.lower() or "администратор" in detail


@patch("duty_teller.api.dependencies.config.MINI_APP_SKIP_AUTH", True)
def test_admin_reassign_403_when_skip_auth(client):
    """PATCH /api/admin/duties/1 with MINI_APP_SKIP_AUTH returns 403."""
    r = client.patch(
        "/api/admin/duties/1",
        json={"user_id": 2},
    )
    assert r.status_code == 403


@patch("duty_teller.api.app.get_duty_by_id")
@patch("duty_teller.api.dependencies.is_admin_for_telegram_user")
@patch("duty_teller.api.dependencies.can_access_miniapp_for_telegram_user")
@patch("duty_teller.api.dependencies.get_user_by_telegram_id")
@patch("duty_teller.api.dependencies.validate_init_data_with_reason")
@patch("duty_teller.api.dependencies.config.MINI_APP_SKIP_AUTH", False)
def test_admin_reassign_404_uses_accept_language_for_detail(
    mock_validate,
    mock_get_user,
    mock_can_access,
    mock_is_admin,
    mock_get_duty,
    client,
):
    """PATCH with Accept-Language: ru returns 404 detail in Russian."""
    from types import SimpleNamespace

    mock_validate.return_value = (1, "admin", "ok", "en")
    mock_get_user.return_value = SimpleNamespace(full_name="Admin", username="admin")
    mock_can_access.return_value = True
    mock_is_admin.return_value = True
    mock_get_duty.return_value = None
    with patch("duty_teller.api.app._lang_from_accept_language") as mock_lang:
        mock_lang.return_value = "ru"
        r = client.patch(
            "/api/admin/duties/999",
            json={"user_id": 2},
            headers={
                "X-Telegram-Init-Data": "auth_date=1&user=%7B%22id%22%3A1%7D&hash=x",
                "Accept-Language": "ru",
            },
        )
    assert r.status_code == 404
    assert r.json()["detail"] == "Дежурство не найдено"
