"""Интеграционные тесты справочника команд `/api/v1/admin/teams` и привязки `users.team_id`.

Покрывает: CRUD команд, 409 на дубль имени, 403 не-админу на новые
эндпоинты, назначение/снятие команды пользователю (в т.ч. явный `null`),
404 на несуществующую команду, обнуление `team_id` при удалении команды,
разрешённое самоизменение `team_id` (в отличие от `role`/`is_blocked`).
"""

import uuid

import httpx
from sqlalchemy.ext.asyncio import AsyncSession

from core.security import create_access_token, hash_password
from models.team import Team
from models.user import User


async def _make_user(session: AsyncSession, *, role: str = "user") -> User:
    user = User(
        email=f"{uuid.uuid4()}@example.com",
        name_user="Team API Tester",
        password_hash=await hash_password("password123"),
        email_verified=True,
        role=role,
    )
    session.add(user)
    await session.flush()
    return user


async def _make_team(session: AsyncSession, *, name: str | None = None) -> Team:
    team = Team(name=name or f"Team {uuid.uuid4()}")
    session.add(team)
    await session.flush()
    return team


def _auth_headers(user: User) -> dict[str, str]:
    token = create_access_token(user.id, user.role)
    return {"Authorization": f"Bearer {token}"}


# --- 403 не-админу -------------------------------------------------------------------


async def test_team_endpoints_forbidden_for_non_admin(
    client: httpx.AsyncClient, db_session: AsyncSession
) -> None:
    user = await _make_user(db_session)
    team = await _make_team(db_session)
    await db_session.commit()
    headers = _auth_headers(user)

    requests = [
        ("GET", "/api/v1/admin/teams", None),
        ("POST", "/api/v1/admin/teams", {"name": "X"}),
        ("PATCH", f"/api/v1/admin/teams/{team.id}", {"name": "Y"}),
        ("DELETE", f"/api/v1/admin/teams/{team.id}", None),
    ]
    for method, path, body in requests:
        response = await client.request(method, path, json=body, headers=headers)
        assert response.status_code == 403, f"{method} {path} -> {response.status_code}"


# --- CRUD команд ---------------------------------------------------------------------


async def test_create_and_list_teams_sorted_by_name(
    client: httpx.AsyncClient, db_session: AsyncSession
) -> None:
    """Список отсортирован по названию; сравниваем взаимный порядок и наличие
    своих (уникальных по суффиксу) команд, а не полный список — в общей
    dev-БД могут быть и другие, легитимные команды разработчика."""
    admin = await _make_user(db_session, role="admin")
    suffix = uuid.uuid4().hex[:8]
    name_alpha, name_beta, name_zeta = f"Alpha-{suffix}", f"Beta-{suffix}", f"Zeta-{suffix}"
    await _make_team(db_session, name=name_zeta)
    await _make_team(db_session, name=name_alpha)
    await db_session.commit()

    response = await client.post(
        "/api/v1/admin/teams", json={"name": name_beta}, headers=_auth_headers(admin)
    )
    assert response.status_code == 201, response.text
    created = response.json()
    assert created["name"] == name_beta
    assert "id" in created and "created_at" in created

    list_response = await client.get("/api/v1/admin/teams", headers=_auth_headers(admin))
    assert list_response.status_code == 200
    body = list_response.json()
    names = [item["name"] for item in body["items"]]
    assert names == sorted(names)
    assert name_alpha in names and name_beta in names and name_zeta in names
    assert names.index(name_alpha) < names.index(name_beta) < names.index(name_zeta)
    assert body["total"] == len(body["items"])


async def test_create_team_strips_name(client: httpx.AsyncClient, db_session: AsyncSession) -> None:
    admin = await _make_user(db_session, role="admin")
    await db_session.commit()

    response = await client.post(
        "/api/v1/admin/teams", json={"name": "  Padded  "}, headers=_auth_headers(admin)
    )
    assert response.status_code == 201, response.text
    assert response.json()["name"] == "Padded"


async def test_create_team_duplicate_name_returns_409(
    client: httpx.AsyncClient, db_session: AsyncSession
) -> None:
    admin = await _make_user(db_session, role="admin")
    name = f"Duplicate {uuid.uuid4()}"
    await _make_team(db_session, name=name)
    await db_session.commit()

    response = await client.post(
        "/api/v1/admin/teams", json={"name": name}, headers=_auth_headers(admin)
    )
    assert response.status_code == 409


async def test_update_team_renames(client: httpx.AsyncClient, db_session: AsyncSession) -> None:
    admin = await _make_user(db_session, role="admin")
    team = await _make_team(db_session, name=f"Old Name {uuid.uuid4()}")
    await db_session.commit()

    new_name = f"New Name {uuid.uuid4()}"
    response = await client.patch(
        f"/api/v1/admin/teams/{team.id}", json={"name": new_name}, headers=_auth_headers(admin)
    )
    assert response.status_code == 200, response.text
    assert response.json()["name"] == new_name


async def test_update_team_duplicate_name_returns_409(
    client: httpx.AsyncClient, db_session: AsyncSession
) -> None:
    admin = await _make_user(db_session, role="admin")
    name_a = f"Team A {uuid.uuid4()}"
    team_a = await _make_team(db_session, name=name_a)
    team_b = await _make_team(db_session, name=f"Team B {uuid.uuid4()}")
    await db_session.commit()

    response = await client.patch(
        f"/api/v1/admin/teams/{team_b.id}",
        json={"name": name_a},
        headers=_auth_headers(admin),
    )
    assert response.status_code == 409
    assert team_a.name == name_a


async def test_update_team_unknown_returns_404(
    client: httpx.AsyncClient, db_session: AsyncSession
) -> None:
    admin = await _make_user(db_session, role="admin")
    await db_session.commit()

    response = await client.patch(
        f"/api/v1/admin/teams/{uuid.uuid4()}", json={"name": "X"}, headers=_auth_headers(admin)
    )
    assert response.status_code == 404


async def test_delete_team_unknown_returns_404(
    client: httpx.AsyncClient, db_session: AsyncSession
) -> None:
    admin = await _make_user(db_session, role="admin")
    await db_session.commit()

    response = await client.delete(
        f"/api/v1/admin/teams/{uuid.uuid4()}", headers=_auth_headers(admin)
    )
    assert response.status_code == 404


async def test_delete_team_clears_user_team_id(
    client: httpx.AsyncClient, db_session: AsyncSession
) -> None:
    admin = await _make_user(db_session, role="admin")
    team = await _make_team(db_session)
    member = await _make_user(db_session)
    member.team_id = team.id
    await db_session.commit()

    response = await client.delete(f"/api/v1/admin/teams/{team.id}", headers=_auth_headers(admin))
    assert response.status_code == 204

    await db_session.refresh(member)
    assert member.team_id is None


# --- Привязка пользователя к команде ---------------------------------------------------


async def test_patch_user_assigns_team(client: httpx.AsyncClient, db_session: AsyncSession) -> None:
    admin = await _make_user(db_session, role="admin")
    target = await _make_user(db_session)
    team = await _make_team(db_session)
    await db_session.commit()

    response = await client.patch(
        f"/api/v1/admin/users/{target.id}",
        json={"team_id": str(team.id)},
        headers=_auth_headers(admin),
    )
    assert response.status_code == 200, response.text
    assert response.json()["team_id"] == str(team.id)


async def test_patch_user_clears_team_with_explicit_null(
    client: httpx.AsyncClient, db_session: AsyncSession
) -> None:
    admin = await _make_user(db_session, role="admin")
    team = await _make_team(db_session)
    target = await _make_user(db_session)
    target.team_id = team.id
    await db_session.commit()

    response = await client.patch(
        f"/api/v1/admin/users/{target.id}",
        json={"team_id": None},
        headers=_auth_headers(admin),
    )
    assert response.status_code == 200, response.text
    assert response.json()["team_id"] is None


async def test_patch_user_missing_team_field_keeps_current_team(
    client: httpx.AsyncClient, db_session: AsyncSession
) -> None:
    admin = await _make_user(db_session, role="admin")
    team = await _make_team(db_session)
    target = await _make_user(db_session)
    target.team_id = team.id
    await db_session.commit()

    response = await client.patch(
        f"/api/v1/admin/users/{target.id}",
        json={"role": "admin"},
        headers=_auth_headers(admin),
    )
    assert response.status_code == 200, response.text
    assert response.json()["team_id"] == str(team.id)


async def test_patch_user_unknown_team_returns_404(
    client: httpx.AsyncClient, db_session: AsyncSession
) -> None:
    admin = await _make_user(db_session, role="admin")
    target = await _make_user(db_session)
    await db_session.commit()

    response = await client.patch(
        f"/api/v1/admin/users/{target.id}",
        json={"team_id": str(uuid.uuid4())},
        headers=_auth_headers(admin),
    )
    assert response.status_code == 404


async def test_admin_can_change_own_team_id(
    client: httpx.AsyncClient, db_session: AsyncSession
) -> None:
    admin = await _make_user(db_session, role="admin")
    team = await _make_team(db_session)
    await db_session.commit()

    response = await client.patch(
        f"/api/v1/admin/users/{admin.id}",
        json={"team_id": str(team.id)},
        headers=_auth_headers(admin),
    )
    assert response.status_code == 200, response.text
    assert response.json()["team_id"] == str(team.id)


async def test_admin_still_forbidden_to_change_own_role_and_block(
    client: httpx.AsyncClient, db_session: AsyncSession
) -> None:
    admin = await _make_user(db_session, role="admin")
    await db_session.commit()

    response = await client.patch(
        f"/api/v1/admin/users/{admin.id}",
        json={"is_blocked": True},
        headers=_auth_headers(admin),
    )
    assert response.status_code == 409

    response = await client.patch(
        f"/api/v1/admin/users/{admin.id}",
        json={"role": "user"},
        headers=_auth_headers(admin),
    )
    assert response.status_code == 409
