from app.models.chunk import DocChunk
from app.services import chunk_payload_service


def _chunk(**overrides) -> DocChunk:
    payload = {
        "chunk_id": "chk_1",
        "repo_id": "ad/repo",
        "doc_path": "docs/api.md",
        "section_title": "API",
        "chunk_text": "Line one.\r\nLine two.   ",
        "chunk_hash": "hash_1",
        "source_type": "repo_doc",
        "metadata": {
            "chunk_type": "text",
            "section_level": 2,
            "chunk_order": 1,
            "section_index": 0,
            "chunk_index": 0,
            "heading_path": "API",
        },
    }
    payload.update(overrides)
    return DocChunk(**payload)


def test_build_embedding_payloads_includes_required_fields():
    chunk = _chunk()

    payloads = chunk_payload_service.build_embedding_payloads(
        [chunk],
        snapshot_id="snap_1",
        document_id="doc_1",
    )

    assert len(payloads) == 1
    payload = payloads[0]
    assert payload["chunk_id"] == "chk_1"
    assert payload["repo_id"] == "ad/repo"
    assert payload["snapshot_id"] == "snap_1"
    assert payload["document_id"] == "doc_1"
    assert payload["source_type"] == "repo_doc"
    assert payload["chunk_type"] == "text"
    assert payload["doc_path"] == "docs/api.md"
    assert payload["section_title"] == "API"
    assert payload["section_level"] == 2
    assert payload["chunk_hash"] == "hash_1"
    assert payload["metadata"]["heading_path"] == "API"


def test_build_embedding_payloads_normalizes_chunk_text():
    chunk = _chunk(chunk_text="First line.\r\nSecond line.   \n")

    payload = chunk_payload_service.build_embedding_payloads(
        [chunk],
        snapshot_id="snap_1",
        document_id="doc_1",
    )[0]

    assert payload["chunk_text"] == "First line.\nSecond line."


def test_build_embedding_payloads_are_ordered_deterministically():
    first = _chunk(
        chunk_id="chk_b",
        metadata={"chunk_type": "text", "section_level": 1, "chunk_order": 2, "section_index": 1, "chunk_index": 0},
    )
    second = _chunk(
        chunk_id="chk_a",
        metadata={"chunk_type": "text", "section_level": 1, "chunk_order": 1, "section_index": 0, "chunk_index": 0},
    )

    payloads = chunk_payload_service.build_embedding_payloads(
        [first, second],
        snapshot_id="snap_1",
        document_id="doc_1",
    )

    assert [item["chunk_id"] for item in payloads] == ["chk_a", "chk_b"]


def test_build_embedding_payloads_skip_invalid_chunks(monkeypatch):
    valid = _chunk(chunk_id="chk_ok", chunk_hash="hash_ok")
    invalid = _chunk(chunk_id="chk_bad", chunk_text="   ")
    calls = []

    class _BindLogger:
        def warning(self, msg):
            calls.append(msg)

        def info(self, _msg):
            return None

    class _Logger:
        def bind(self, **_kwargs):
            return _BindLogger()

    monkeypatch.setattr(chunk_payload_service, "logger", _Logger())

    payloads = chunk_payload_service.build_embedding_payloads(
        [valid, invalid],
        snapshot_id="snap_1",
        document_id="doc_1",
    )

    assert [item["chunk_id"] for item in payloads] == ["chk_ok"]
    assert any("Skipping malformed embedding payload" in msg for msg in calls)


def test_build_embedding_payloads_prevent_duplicates_within_snapshot(monkeypatch):
    first = _chunk(chunk_id="chk_1", chunk_hash="hash_1")
    duplicate = _chunk(chunk_id="chk_1", chunk_hash="hash_1b")
    calls = []

    class _BindLogger:
        def warning(self, msg):
            calls.append(msg)

        def info(self, _msg):
            return None

    class _Logger:
        def bind(self, **_kwargs):
            return _BindLogger()

    monkeypatch.setattr(chunk_payload_service, "logger", _Logger())

    payloads = chunk_payload_service.build_embedding_payloads(
        [first, duplicate],
        snapshot_id="snap_1",
        document_id="doc_1",
    )

    assert len(payloads) == 1
    assert payloads[0]["chunk_id"] == "chk_1"
    assert any("Duplicate embedding payload detected" in msg for msg in calls)


def test_serialize_embedding_payload_is_deterministic():
    payload = chunk_payload_service.build_embedding_payloads(
        [_chunk(metadata={"section_level": 2, "chunk_type": "text", "z": 1, "a": 2})],
        snapshot_id="snap_1",
        document_id="doc_1",
    )[0]

    serialized1 = chunk_payload_service.serialize_embedding_payload(payload)
    serialized2 = chunk_payload_service.serialize_embedding_payload(payload)

    assert serialized1 == serialized2
    assert '"snapshot_id":"snap_1"' in serialized1
    assert '"document_id":"doc_1"' in serialized1


def test_serialize_embedding_payload_rejects_invalid_payload():
    bad_payload = {
        "chunk_id": "bad id with spaces",
        "repo_id": "ad/repo",
        "snapshot_id": "snap_1",
        "document_id": "doc_1",
        "source_type": "repo_doc",
        "chunk_type": "text",
        "doc_path": "docs/api.md",
        "section_title": "API",
        "section_level": 1,
        "chunk_text": "ok",
        "chunk_hash": "hash_1",
        "metadata": {},
    }

    try:
        chunk_payload_service.serialize_embedding_payload(bad_payload)
        assert False
    except ValueError:
        assert True
