import asyncio

import pytest

from app.models.enums import ChunkType, EmbedStatus, SourceType
from app.models.stored_embedding_record import StoredEmbeddingRecord
from app.models.upstream.code_chunk import CodeChunkDocument
from app.models.upstream.commit_analysis import CommitAnalysisDocument
from app.models.upstream.doc_chunk import DocChunkDocument
from app.services import embedding_record_builder


DOC_CHUNK = DocChunkDocument(
    chunk_id="chk_ad_example_repo_docs_arch_abc123",
    repo_id="AD/example-repo",
    doc_path="docs/architecture.md",
    section_title="Overview",
    chunk_text="The service exposes a REST API on port 6001.",
    chunk_hash="sha256:doc1",
    metadata={
        "snapshot_id": "snap_abc123",
        "commit_hash": "a1b2c3d4e5f6",
        "section_level": 2,
    },
)

CODE_CHUNK = CodeChunkDocument(
    chunk_id="chk_sym_config_go_Load",
    repo_id="AD/example-repo",
    snapshot_id="snap_abc123",
    commit_sha="a1b2c3d4e5f6",
    file_path="internal/config/config.go",
    symbol_name="Load",
    symbol_type="function",
    text="func Load(path string) (*Config, error) { ... }",
    start_line=24,
    end_line=58,
    chunk_hash="sha256:code1",
    language="go",
)

COMMIT_ANALYSIS = CommitAnalysisDocument(
    analysis_id="analysis_b2c3d4",
    repo_id="AD/example-repo",
    commit_sha="b2c3d4e5f6a7",
    summary="Introduced Reload and refactored configuration loading.",
    impacted_symbols=["Load", "Reload"],
    changed_files=["internal/config/config.go"],
)


def test_build_from_doc_chunk_maps_pipeline_fields():
    record = embedding_record_builder.build_from_doc_chunk(
        DOC_CHUNK,
        snapshot_id="snap_abc123",
        commit_sha="a1b2c3d4e5f6",
        artifact_uri="mongo://snapshot_graphs/art_snap_abc123_merged",
    )

    assert record.record_id == "emb_chk_ad_example_repo_docs_arch_abc123"
    assert record.source_type == SourceType.DOCS
    assert record.chunk_type.value == "text"
    assert record.graph_node_id == "doc_chunk_chk_ad_example_repo_docs_arch_abc123"
    assert record.metadata.chunk_hash == "sha256:doc1"


def test_build_from_code_chunk_maps_pipeline_fields():
    record = embedding_record_builder.build_from_code_chunk(
        CODE_CHUNK,
        artifact_uri="mongo://snapshot_graphs/art_snap_abc123_merged",
    )

    assert record.record_id == "emb_chk_sym_config_go_Load"
    assert record.source_type == SourceType.CODE
    assert record.chunk_type.value == "symbol"
    assert record.graph_node_id == "sym_config_go_Load"
    assert record.symbol_name == "Load"


def test_build_from_commit_analysis_maps_pipeline_fields():
    record = embedding_record_builder.build_from_commit_analysis(
        COMMIT_ANALYSIS,
        snapshot_id="snap_abc123",
    )

    assert record.record_id == "emb_analysis_b2c3d4"
    assert record.source_type == SourceType.COMMIT
    assert record.chunk_type.value == "summary"
    assert record.metadata.impacted_symbols == ["Load", "Reload"]


def test_graph_node_id_helpers():
    assert (
        embedding_record_builder.graph_node_id_for_code_chunk("chk_sym_config_go_Load")
        == "sym_config_go_Load"
    )
    assert (
        embedding_record_builder.graph_node_id_for_doc_chunk("chk_docs_1")
        == "doc_chunk_chk_docs_1"
    )


def test_build_snapshot_records_combines_docs_and_code(monkeypatch):
    async def _list_docs(_repo_id, _snapshot_id):
        return [DOC_CHUNK]

    async def _list_code(_repo_id, _snapshot_id):
        return [CODE_CHUNK]

    async def _existing(*_args, **_kwargs):
        return {}

    async def _graph(_repo_id, _snapshot_id):
        return None

    monkeypatch.setattr(embedding_record_builder.doc_chunk_repo, "list_by_snapshot", _list_docs)
    monkeypatch.setattr(embedding_record_builder.code_chunk_repo, "list_by_snapshot", _list_code)
    monkeypatch.setattr(embedding_record_builder, "_load_existing_records", _existing)
    monkeypatch.setattr(embedding_record_builder.snapshot_graph_repo, "get_merged", _graph)

    result = asyncio.run(
        embedding_record_builder.build_snapshot_records(
            "AD/example-repo",
            "snap_abc123",
            "a1b2c3d4e5f6",
            artifact_uri="mongo://snapshot_graphs/art_snap_abc123_merged",
        )
    )

    assert result.built_count == 3
    assert result.doc_source_count == 1
    assert result.code_source_count == 1
    source_types = {item.record.source_type for item in result.items}
    assert source_types == {SourceType.DOCS, SourceType.CODE}


def test_build_snapshot_records_handles_empty_sources(monkeypatch):
    async def _empty(*_args, **_kwargs):
        return []

    async def _existing(*_args, **_kwargs):
        return {}

    monkeypatch.setattr(embedding_record_builder.doc_chunk_repo, "list_by_snapshot", _empty)
    monkeypatch.setattr(embedding_record_builder.code_chunk_repo, "list_by_snapshot", _empty)
    monkeypatch.setattr(embedding_record_builder, "_load_existing_records", _existing)

    result = asyncio.run(
        embedding_record_builder.build_snapshot_records(
            "AD/example-repo",
            "snap_abc123",
            "a1b2c3d4e5f6",
        )
    )

    assert result.built_count == 0
    assert result.total_source_count == 0


def test_build_snapshot_records_skips_empty_and_unchanged(monkeypatch):
    empty_doc = DOC_CHUNK.model_copy(update={"chunk_id": "chk_empty", "chunk_text": "   "})
    unchanged_doc = DOC_CHUNK.model_copy(
        update={"chunk_id": "chk_unchanged", "chunk_hash": "sha256:same"}
    )

    async def _list_docs(_repo_id, _snapshot_id):
        return [empty_doc, unchanged_doc, DOC_CHUNK]

    async def _list_code(_repo_id, _snapshot_id):
        return []

    existing = StoredEmbeddingRecord(
        record_id="emb_chk_unchanged",
        repo_id="AD/example-repo",
        snapshot_id="snap_abc123",
        commit_sha="a1b2c3d4e5f6",
        source_type=SourceType.DOCS,
        chunk_type=ChunkType.TEXT,
        text="old",
        doc_path="docs/architecture.md",
        section_title="Overview",
        graph_node_id="doc_chunk_chk_unchanged",
        dedupe_key="dedupe",
        embed_status=EmbedStatus.EMBEDDED,
        metadata={"chunk_hash": "sha256:same"},
    )

    async def _existing(*_args, **_kwargs):
        return {existing.record_id: existing}

    monkeypatch.setattr(embedding_record_builder.doc_chunk_repo, "list_by_snapshot", _list_docs)
    monkeypatch.setattr(embedding_record_builder.code_chunk_repo, "list_by_snapshot", _list_code)
    monkeypatch.setattr(embedding_record_builder, "_load_existing_records", _existing)

    result = asyncio.run(
        embedding_record_builder.build_snapshot_records(
            "AD/example-repo",
            "snap_abc123",
            "a1b2c3d4e5f6",
            skip_unchanged=True,
        )
    )

    assert result.skipped_empty == 1
    assert result.skipped_unchanged == 1
    assert result.built_count == 1
    assert result.items[0].record.record_id == "emb_chk_ad_example_repo_docs_arch_abc123"


def test_build_commit_record(monkeypatch):
    async def _get_analysis(_analysis_id):
        return COMMIT_ANALYSIS

    async def _existing(*_args, **_kwargs):
        return {}

    monkeypatch.setattr(
        embedding_record_builder.commit_analysis_repo,
        "get_by_analysis_id",
        _get_analysis,
    )
    monkeypatch.setattr(embedding_record_builder, "_load_existing_records", _existing)

    result = asyncio.run(
        embedding_record_builder.build_commit_record(
            "AD/example-repo",
            "analysis_b2c3d4",
            snapshot_id="snap_abc123",
        )
    )

    assert result.built_count == 1
    assert result.items[0].upstream_analysis_id == "analysis_b2c3d4"
    assert result.items[0].record.source_type == SourceType.COMMIT


def test_build_commit_record_skips_missing_analysis(monkeypatch):
    async def _get_analysis(_analysis_id):
        return None

    monkeypatch.setattr(
        embedding_record_builder.commit_analysis_repo,
        "get_by_analysis_id",
        _get_analysis,
    )

    result = asyncio.run(
        embedding_record_builder.build_commit_record(
            "AD/example-repo",
            "missing",
        )
    )

    assert result.built_count == 0


def test_build_commit_record_uses_commit_sha_when_snapshot_missing(monkeypatch):
    async def _get_analysis(_analysis_id):
        return COMMIT_ANALYSIS

    async def _existing(*_args, **_kwargs):
        return {}

    monkeypatch.setattr(
        embedding_record_builder.commit_analysis_repo,
        "get_by_analysis_id",
        _get_analysis,
    )
    monkeypatch.setattr(embedding_record_builder, "_load_existing_records", _existing)

    result = asyncio.run(
        embedding_record_builder.build_commit_record(
            "AD/example-repo",
            "analysis_b2c3d4",
        )
    )

    assert result.items[0].record.snapshot_id == COMMIT_ANALYSIS.commit_sha
