"""Unit tests for short_id generation, validation, and repository lookup."""

import re
from unittest.mock import AsyncMock, MagicMock

import pytest
from fastapi import HTTPException

from app.routers.images import _validate_short_id
from app.utils import generate_short_id

_SHORT_ID_RE = re.compile(r"^[a-zA-Z0-9]{8}$")


def test_validate_short_id_accepts_valid():
    _validate_short_id("AbCd1234")  # must not raise


def test_validate_short_id_rejects_too_long():
    with pytest.raises(HTTPException) as exc:
        _validate_short_id("toolong!!")
    assert exc.value.status_code == 422


def test_validate_short_id_rejects_too_short():
    with pytest.raises(HTTPException) as exc:
        _validate_short_id("short")
    assert exc.value.status_code == 422


def test_validate_short_id_rejects_invalid_chars():
    with pytest.raises(HTTPException) as exc:
        _validate_short_id("has spa!")
    assert exc.value.status_code == 422


def test_generate_short_id_unique():
    ids = {generate_short_id() for _ in range(100)}
    assert len(ids) > 90  # collision in 100 draws would be astronomically unlikely


def test_repo_get_by_short_id_uses_correct_field():
    """get_by_short_id selects on Image.short_id, not Image.id."""
    import asyncio

    from app.repositories.image_repo import ImageRepository

    mock_session = MagicMock()
    scalar = MagicMock()
    scalar.scalar_one_or_none = MagicMock(return_value=None)
    mock_session.execute = AsyncMock(return_value=scalar)

    repo = ImageRepository(mock_session)
    asyncio.get_event_loop().run_until_complete(repo.get_by_short_id("AbCd1234"))

    call_args = mock_session.execute.call_args[0][0]
    compiled = call_args.compile(compile_kwargs={"literal_binds": True})
    assert "short_id" in str(compiled)
    assert "AbCd1234" in str(compiled)
