"""Importer integration tests.

These exercise the real Importer against a real Postgres + a real filesystem
(via tmp_path). The DB fixture rolls back after each test so the import_root
fixture is fresh per-test.
"""

from pathlib import Path

import pytest
from PIL import Image
from sqlalchemy import select

from backend.app.models import Artist, ImageRecord, ImportSettings
from backend.app.services.importer import Importer, SkipReason
from backend.app.services.thumbnailer import Thumbnailer

pytestmark = pytest.mark.integration


@pytest.fixture
def import_layout(tmp_path):
    import_root = tmp_path / "import"
    images_root = tmp_path / "images"
    import_root.mkdir()
    images_root.mkdir()
    return import_root, images_root


def _make_jpeg(path: Path, size: tuple[int, int] = (800, 600)):
    path.parent.mkdir(parents=True, exist_ok=True)
    Image.new("RGB", size, color=(120, 200, 80)).save(path, "JPEG")


def _make_png_rgba(path: Path, size: tuple[int, int], alpha: int):
    path.parent.mkdir(parents=True, exist_ok=True)
    Image.new("RGBA", size, color=(120, 200, 80, alpha)).save(path, "PNG")


@pytest.fixture
def importer(db_sync, import_layout):
    import_root, images_root = import_layout
    settings = db_sync.execute(select(ImportSettings).where(ImportSettings.id == 1)).scalar_one()
    thumbnailer = Thumbnailer(images_root=images_root)
    return Importer(
        session=db_sync,
        images_root=images_root,
        import_root=import_root,
        thumbnailer=thumbnailer,
        settings=settings,
    )


def test_import_one_happy_path(importer, import_layout):
    import_root, images_root = import_layout
    src = import_root / "Alice" / "first.jpg"
    _make_jpeg(src)
    result = importer.import_one(src)
    assert result.status == "imported"
    record = importer.session.get(ImageRecord, result.image_id)
    assert record.path.startswith(str(images_root / "Alice"))
    assert record.path.endswith(".jpg")


def test_folder_creates_artist_and_links_artist_id(importer, import_layout):
    # FC-2d-vii-c: folder import creates the Artist and sets the canonical
    # image_record.artist_id — no artist-kind Tag (that path was retired;
    # the no-tag invariant is covered by test_importer_artist_id.py).
    import_root, _ = import_layout
    src = import_root / "Alice" / "first.jpg"
    _make_jpeg(src)
    result = importer.import_one(src)

    artist = importer.session.execute(
        select(Artist).where(Artist.slug == "alice")
    ).scalar_one()
    assert artist.name == "Alice"

    record = importer.session.get(ImageRecord, result.image_id)
    assert record.artist_id == artist.id


def test_dedup_by_hash(importer, import_layout):
    import_root, _ = import_layout
    src1 = import_root / "Alice" / "first.jpg"
    src2 = import_root / "Bob" / "duplicate.jpg"
    _make_jpeg(src1)
    src2.parent.mkdir(parents=True, exist_ok=True)
    src2.write_bytes(src1.read_bytes())

    r1 = importer.import_one(src1)
    r2 = importer.import_one(src2)
    assert r1.status == "imported"
    assert r2.status == "skipped"
    assert r2.skip_reason == SkipReason.duplicate_hash


def test_min_width_filter(importer, import_layout):
    import_root, _ = import_layout
    importer.settings.min_width = 1000  # mutate the in-memory copy for this test
    src = import_root / "small.jpg"
    _make_jpeg(src, size=(500, 500))
    result = importer.import_one(src)
    assert result.status == "skipped"
    assert result.skip_reason == SkipReason.too_small


def test_transparent_filter(importer, import_layout):
    import_root, _ = import_layout
    importer.settings.skip_transparent = True
    importer.settings.transparency_threshold = 0.5
    src = import_root / "ghost.png"
    _make_png_rgba(src, size=(100, 100), alpha=0)  # fully transparent
    result = importer.import_one(src)
    assert result.status == "skipped"
    assert result.skip_reason == SkipReason.too_transparent


def test_single_color_filter(importer, import_layout):
    """The skip_single_color setting existed since FC-2 but was never wired
    (the audit module's docstring said so); wired 2026-07-02 using the same
    canonical predicate as the Cleanup audit. Solid fill skips when enabled,
    imports when disabled (the default)."""
    from PIL import Image as PILImage

    import_root, _ = import_layout
    solid = import_root / "solid.png"
    solid.parent.mkdir(parents=True, exist_ok=True)
    PILImage.new("RGB", (100, 100), (12, 34, 56)).save(solid)

    importer.settings.skip_single_color = True
    importer.settings.single_color_threshold = 0.95
    result = importer.import_one(solid)
    assert result.status == "skipped"
    assert result.skip_reason == SkipReason.single_color

    importer.settings.skip_single_color = False
    solid2 = import_root / "solid2.png"
    PILImage.new("RGB", (100, 100), (200, 10, 10)).save(solid2)
    assert importer.import_one(solid2).status == "imported"


def test_unsupported_extension(importer, import_layout):
    # FC-2d-iii: non-media is no longer skipped — it's captured as a
    # PostAttachment so nothing a post contained is lost.
    import_root, _ = import_layout
    src = import_root / "Alice" / "notes.txt"
    src.parent.mkdir(parents=True, exist_ok=True)
    src.write_text("hello")
    result = importer.import_one(src)
    assert result.status == "attached"


def test_root_level_file_has_no_artist(importer, import_layout):
    import_root, _ = import_layout
    src = import_root / "loose.jpg"
    _make_jpeg(src)
    importer.import_one(src)
    artists = importer.session.execute(select(Artist)).scalars().all()
    assert artists == []


def test_pil_load_oserror_in_transparency_check_skips_not_raises(
    importer, import_layout, monkeypatch,
):
    """PIL.verify() only validates header structure — broken pixel data
    only surfaces when load() actually decodes. The importer must catch
    the OSError and return a skipped: invalid_image result so the Celery
    autoretry loop doesn't bounce the same broken file forever.
    Operator hit this 2026-05-25 with a corrupt JPEG in the IR set."""
    import_root, _ = import_layout
    src = import_root / "Bob" / "corrupt.png"
    # Make a real RGBA PNG so the has_alpha path engages.
    _make_png_rgba(src, (100, 100), alpha=128)

    importer.settings.skip_transparent = True
    importer.settings.transparency_threshold = 0.5

    # Force the next _transparency_pct call to raise as if PIL's load()
    # blew up on truncated pixel data.
    def _boom(_self, _src):
        raise OSError("broken data stream when reading image file")
    monkeypatch.setattr(
        type(importer), "_transparency_pct", _boom,
    )

    result = importer.import_one(src)
    assert result.status == "skipped"
    assert result.skip_reason == SkipReason.invalid_image
    assert "transparency check" in (result.error or "")


def test_pil_load_oserror_in_phash_compute_skips_not_raises(
    importer, import_layout, monkeypatch,
):
    """Same shape as the transparency-check guard, but for the phash
    compute block — the OTHER place PIL.load() runs implicitly during
    the dedup pipeline."""
    import_root, _ = import_layout
    src = import_root / "Carol" / "corrupt.jpg"
    _make_jpeg(src)

    # Disable transparency check so we reach the phash compute block.
    importer.settings.skip_transparent = False

    from backend.app.services import importer as importer_module

    def _boom(_im):
        raise OSError("broken data stream when reading image file")
    monkeypatch.setattr(importer_module, "compute_phash", _boom)

    result = importer.import_one(src)
    assert result.status == "skipped"
    assert result.skip_reason == SkipReason.invalid_image
    assert "phash compute" in (result.error or "")
