import json
import tempfile
import unittest
from pathlib import Path

from gnos.core.materialize import (
    MaterializeError,
    materialize_collection,
    materialize_doc_id,
    _reconstruct_bytes,
)
from gnos.core.store import ObjectStore
from gnos.index.local import LocalIndex
from gnos.manifest.schema import DocManifest
from gnos.manifest.codec import dump_manifest_bytes, dump_collection_bytes
from gnos.manifest.schema import CollectionManifest, CollectionEntry
from gnos.core.hash import sha256_hex


class TestMaterializeMoreErrors(unittest.TestCase):
    def test_materialize_doc_no_manifests(self) -> None:
        with tempfile.TemporaryDirectory() as tmp:
            root = Path(tmp)
            store = ObjectStore(root / ".gnos")
            store.init()
            index = LocalIndex(root / ".gnos" / "index.json")
            index.path.write_text(json.dumps({"documents": {"d" * 64: {"manifests": []}}}), encoding="utf-8")
            with self.assertRaises(MaterializeError):
                materialize_doc_id("d" * 64, store, index, root / "out.md")

    def test_materialize_doc_manifest_missing_in_store(self) -> None:
        with tempfile.TemporaryDirectory() as tmp:
            root = Path(tmp)
            store = ObjectStore(root / ".gnos")
            store.init()
            index = LocalIndex(root / ".gnos" / "index.json")
            index.path.write_text(
                json.dumps({"documents": {"d" * 64: {"manifests": ["m" * 64]}}}),
                encoding="utf-8",
            )
            with self.assertRaises(MaterializeError):
                materialize_doc_id("d" * 64, store, index, root / "out.md")

    def test_materialize_collection_missing_collection(self) -> None:
        with tempfile.TemporaryDirectory() as tmp:
            root = Path(tmp)
            store = ObjectStore(root / ".gnos")
            store.init()
            index = LocalIndex(root / ".gnos" / "index.json")
            with self.assertRaises(MaterializeError):
                materialize_collection("0" * 64, store, index, root / "out")

    def test_materialize_collection_allows_empty_existing_dir(self) -> None:
        with tempfile.TemporaryDirectory() as tmp:
            root = Path(tmp)
            store = ObjectStore(root / ".gnos")
            store.init()
            index = LocalIndex(root / ".gnos" / "index.json")

            # Create a trivially materializable collection with a single doc.
            content = b"x"
            doc_id = sha256_hex(content)
            chunk_hash = sha256_hex(content)
            store.put(chunk_hash, content)

            manifest = DocManifest(
                type="doc-manifest",
                version=1,
                doc_id=doc_id,
                format="text",
                chunking={"method": "sequential-v1", "params": {"chunk_size": 1024}},
                chunks=[{"hash": chunk_hash}],
            )
            manifest_bytes = dump_manifest_bytes(manifest)
            manifest_hash = sha256_hex(manifest_bytes)
            store.put(manifest_hash, manifest_bytes)
            index.record_manifest(doc_id, manifest_hash, "text", manifest.chunking, manifest.chunks)

            collection = CollectionManifest(
                type="collection-manifest",
                version=1,
                documents=[CollectionEntry(doc_id=doc_id, path="a.txt")],
            )
            collection_bytes = dump_collection_bytes(collection)
            collection_id = sha256_hex(collection_bytes)
            store.put(collection_id, collection_bytes)

            out_dir = root / "out"
            out_dir.mkdir()
            result = materialize_collection(collection_id, store, index, out_dir)
            self.assertEqual(result.collection_id, collection_id)
            self.assertTrue((out_dir / "a.txt").exists())

    def test_reconstruct_bytes_missing_chunk(self) -> None:
        with tempfile.TemporaryDirectory() as tmp:
            root = Path(tmp)
            store = ObjectStore(root / ".gnos")
            store.init()
            manifest = DocManifest(
                type="doc-manifest",
                version=1,
                doc_id="0" * 64,
                format="text",
                chunking={"method": "sequential-v1", "params": {"chunk_size": 1}},
                chunks=[{"hash": "a" * 64}],
            )
            with self.assertRaises(MaterializeError):
                _reconstruct_bytes(manifest, store)

    def test_reconstruct_bytes_hash_mismatch(self) -> None:
        with tempfile.TemporaryDirectory() as tmp:
            root = Path(tmp)
            store = ObjectStore(root / ".gnos")
            store.init()
            claimed_hash = sha256_hex(b"x")
            store.put(claimed_hash, b"y")
            manifest = DocManifest(
                type="doc-manifest",
                version=1,
                doc_id="0" * 64,
                format="text",
                chunking={"method": "sequential-v1", "params": {"chunk_size": 1}},
                chunks=[{"hash": claimed_hash}],
            )
            with self.assertRaises(MaterializeError):
                _reconstruct_bytes(manifest, store)


if __name__ == "__main__":
    unittest.main()
