import pytest
from sqlalchemy import func, select

from backend.app.models import ImageRecord, TagKind
from backend.app.models.series_page import SeriesPage
from backend.app.services.series_service import SeriesError, SeriesService
from backend.app.services.tag_service import TagService

pytestmark = pytest.mark.integration

_SEQ = 0


async def _img(db):
    global _SEQ
    _SEQ += 1
    rec = ImageRecord(
        path=f"/tmp/fc_ss_{_SEQ}.png", sha256=f"{_SEQ:064d}",
        size_bytes=1, mime="image/png", origin="uploaded",
    )
    db.add(rec)
    await db.flush()
    return rec.id


async def _order(db, sid):
    rows = (
        await db.execute(
            select(SeriesPage.image_id, SeriesPage.page_number)
            .where(SeriesPage.series_tag_id == sid)
            .order_by(SeriesPage.page_number)
        )
    ).all()
    return [r.image_id for r in rows]


@pytest.mark.asyncio
async def test_add_appends_in_order(db):
    s = await TagService(db).find_or_create("S", TagKind.series)
    i1, i2 = await _img(db), await _img(db)
    n = await SeriesService(db).add_images(s.id, [i1, i2])
    assert n == 2
    assert await _order(db, s.id) == [i1, i2]


@pytest.mark.asyncio
async def test_add_moves_from_other_series(db):
    svc = SeriesService(db)
    a = await TagService(db).find_or_create("A", TagKind.series)
    b = await TagService(db).find_or_create("B", TagKind.series)
    i1 = await _img(db)
    await svc.add_images(a.id, [i1])
    moved = await svc.add_images(b.id, [i1])
    assert moved == 1
    assert await _order(db, a.id) == []
    assert await _order(db, b.id) == [i1]
    cnt = await db.scalar(
        select(func.count())
        .select_from(SeriesPage).where(SeriesPage.image_id == i1)
    )
    assert cnt == 1


@pytest.mark.asyncio
async def test_add_idempotent_for_same_series(db):
    s = await TagService(db).find_or_create("S2", TagKind.series)
    i1 = await _img(db)
    svc = SeriesService(db)
    await svc.add_images(s.id, [i1])
    again = await svc.add_images(s.id, [i1])
    assert again == 0
    assert await _order(db, s.id) == [i1]


@pytest.mark.asyncio
async def test_remove(db):
    s = await TagService(db).find_or_create("S3", TagKind.series)
    i1, i2 = await _img(db), await _img(db)
    svc = SeriesService(db)
    await svc.add_images(s.id, [i1, i2])
    removed = await svc.remove_images(s.id, [i1])
    assert removed == 1
    assert await _order(db, s.id) == [i2]


@pytest.mark.asyncio
async def test_set_page_number_sparse_with_gaps(db):
    s = await TagService(db).find_or_create("S4", TagKind.series)
    i1, i2, i3 = await _img(db), await _img(db), await _img(db)
    svc = SeriesService(db)
    await svc.add_images(s.id, [i1, i2, i3])   # numbered 1, 2, 3
    # set a sparse number, leaving a gap
    await svc.set_page_number(s.id, i3, 5)
    out = await svc.list_pages(s.id)
    assert [p["page_number"] for p in out["pages"]] == [1, 2, 5]
    assert len(out["gaps"]) == 1
    assert out["gaps"][0]["from"] == 3
    assert out["gaps"][0]["to"] == 4
    # unnumber a page → it sorts to the tail
    await svc.set_page_number(s.id, i1, None)
    out = await svc.list_pages(s.id)
    assert [p["page_number"] for p in out["pages"]] == [2, 5, None]
    with pytest.raises(SeriesError):
        await svc.set_page_number(s.id, 99999, 1)


@pytest.mark.asyncio
async def test_set_cover_makes_page_one_preserving_rest(db):
    s = await TagService(db).find_or_create("S5", TagKind.series)
    i1, i2, i3 = await _img(db), await _img(db), await _img(db)
    svc = SeriesService(db)
    await svc.add_images(s.id, [i1, i2, i3])
    await svc.set_cover(s.id, i3)
    assert await _order(db, s.id) == [i3, i1, i2]


@pytest.mark.asyncio
async def test_list_pages_shape_and_guards(db):
    svc = SeriesService(db)
    s = await TagService(db).find_or_create("S6", TagKind.series)
    i1 = await _img(db)
    await svc.add_images(s.id, [i1])
    out = await svc.list_pages(s.id)
    assert out["series"]["id"] == s.id
    assert out["pages"][0]["image_id"] == i1
    assert out["pages"][0]["page_number"] == 1
    assert out["pages"][0]["thumbnail_url"]
    assert out["pages"][0]["image_url"].startswith("/images/")
    g = await TagService(db).find_or_create("notser", TagKind.general)
    with pytest.raises(SeriesError):
        await svc.list_pages(g.id)
    with pytest.raises(SeriesError):
        await svc.add_images(g.id, [i1])
    with pytest.raises(SeriesError):
        await svc.add_images(s.id, list(range(1, 502)))
