import json
import logging

from app.services.export_service import (
    _SHORT_ID_RE,
    _copy_as_jpeg,
    _safe_json_load,
    _t2i_text_from_variations_json,
    _visible_short_ids_from_json,
)


def test_visible_short_ids_accepts_current_and_legacy_shapes():
    value = json.dumps(
        [
            {"short_id": "C01", "id": "entity-uuid"},
            {"id": "L02"},
            "P03",
            {"id": "C04O05"},
            {"entity_id": "not-a-short-id"},
            17,
        ]
    )

    assert _visible_short_ids_from_json(value) == {"C01", "L02", "P03", "C04"}


def test_visible_short_ids_accepts_wrapped_dict_shape():
    value = {
        "visible_entities": [
            {"short_id": "C07"},
            {"id": "L08"},
            "P09",
        ]
    }

    assert _visible_short_ids_from_json(value) == {"C07", "L08", "P09"}


def test_t2i_text_accepts_rich_variations_and_legacy_prompts():
    value = json.dumps(
        [
            {"variant_label": "base", "t2i_prompt": "C01O02 enters."},
            {"prompt": "Legacy prompt with P03."},
            "raw prompt mentioning L04",
            {"t2i_prompt": ""},
            None,
        ]
    )

    text = _t2i_text_from_variations_json(value)
    assert "C01O02 enters." in text
    assert "Legacy prompt with P03." in text
    assert "raw prompt mentioning L04" in text


def test_t2i_text_uses_fallback_for_malformed_variations():
    assert _t2i_text_from_variations_json("not json {", "fallback prompt") == "fallback prompt"


def test_copy_as_jpeg_fallback_writes_requested_html_path(tmp_path):
    src = tmp_path / "source.not_image"
    src.write_bytes(b"not an image")
    dst = tmp_path / "exported.jpg"

    _copy_as_jpeg(src, dst)

    assert dst.exists()
    assert dst.read_bytes() == b"not an image"


# ─────────────────────────────────────────────
# Codex iter 2 review — fix 회귀 가드
# ─────────────────────────────────────────────


def test_b2_short_id_re_rejects_standalone_o_prefix():
    """Codex B2: regex 가 'O##' 단독은 거부해야 filter 와 일치 (silent drop 회피)."""
    assert _SHORT_ID_RE.fullmatch("O01") is None, (
        "_SHORT_ID_RE 가 'O01' 통과시키면 _visible_short_ids_from_json 의 "
        "startswith filter 가 폐기 → "
        "silent drop. regex 를 filter 에 맞춰 좁혀야."
    )
    assert _SHORT_ID_RE.fullmatch("O123") is None
    # 정상 ID 는 통과해야 함
    assert _SHORT_ID_RE.fullmatch("C01") is not None
    assert _SHORT_ID_RE.fullmatch("L02") is not None
    assert _SHORT_ID_RE.fullmatch("P099") is not None
    assert _SHORT_ID_RE.fullmatch("C04O05") is not None


def test_b2_visible_short_ids_drops_standalone_o_prefix():
    """B2 회귀: 'O01' 단독 ID 는 filter 에서 제거 — 결과 set 에 미포함."""
    value = json.dumps([
        {"short_id": "C01"},
        {"short_id": "O02"},
        "O03",
        "C04",
    ])
    result = _visible_short_ids_from_json(value)
    assert result == {"C01", "C04"}, (
        f"O 단독 ID 가 결과에 포함되면 안 됨. got={result}"
    )


def test_b1_copy_as_jpeg_warns_on_pil_failure(tmp_path, caplog):
    """Codex B1: PIL 실패 시 logger.warning 으로 mismatch 가시화 (silent 회피)."""
    src = tmp_path / "source.not_image"
    src.write_bytes(b"not an image")
    dst = tmp_path / "exported.jpg"

    with caplog.at_level(logging.WARNING, logger="app.services.export_service"):
        _copy_as_jpeg(src, dst)

    assert dst.exists()
    # warning 로그 emitted (silent absorb 차단). 의도된 고정 문구로 assert
    # — substring fragility 회피 (Codex iter 2 review M3).
    messages = [r.getMessage() for r in caplog.records if r.levelno >= logging.WARNING]
    assert any("_copy_as_jpeg PIL conversion failed" in m for m in messages), (
        "PIL 변환 실패 시 '_copy_as_jpeg PIL conversion failed' 진입 문구 누락. "
        f"records={messages}"
    )
    assert any("raw 복사 fallback" in m for m in messages), (
        "fallback 진입 문구 'raw 복사 fallback' 누락. "
        f"records={messages}"
    )


def test_safe_json_load_handles_bytes_and_decoded_shapes():
    """M2 통합: helper 가 None / 빈 문자열 / dict / list / malformed JSON 모두 처리."""
    assert _safe_json_load(None, []) == []
    assert _safe_json_load("", []) == []
    assert _safe_json_load("invalid json {", "default") == "default"
    assert _safe_json_load([1, 2, 3], []) == [1, 2, 3]
    assert _safe_json_load({"k": "v"}, {}) == {"k": "v"}
    # dict-shaped JSON 은 deserialize
    assert _safe_json_load('{"k": 1}', {}) == {"k": 1}
    # bytes 는 string 아니므로 default
    assert _safe_json_load(b'{"k": 1}', "default") == "default"
