"""Tests for StorageBuilder and operation sub-builders."""

import pytest

from storage_core.builder import StorageBuilder

BUCKET = "builder-test"


@pytest.fixture
async def client(fake_client):
    return fake_client


@pytest.fixture
async def builder(client):
    b = StorageBuilder(client)
    await b.connect()
    await b.bucket(BUCKET).create()
    return b


class TestStorageBuilder:
    async def test_ping(self, builder):
        assert await builder.ping() is True

    async def test_async_context_manager(self, fake_client):
        async with StorageBuilder(fake_client) as b:
            await b.bucket("ctx-bucket").create()
            assert await b.bucket("ctx-bucket").exists()


class TestBucketOperation:
    async def test_create_exists_delete(self, builder):
        op = builder.bucket("op-bucket")
        await op.create()
        assert await op.exists() is True
        await op.delete()
        assert await op.exists() is False

    async def test_list_objects(self, builder):
        await builder.object(BUCKET, "f1.txt").put(b"one")
        await builder.object(BUCKET, "f2.txt").put(b"two")

        listing = await builder.bucket(BUCKET).list_objects()
        assert len(listing.objects) == 2

    async def test_list_objects_with_prefix(self, builder):
        await builder.object(BUCKET, "img/a.jpg").put(b"a")
        await builder.object(BUCKET, "img/b.jpg").put(b"b")
        await builder.object(BUCKET, "doc/c.txt").put(b"c")

        listing = await builder.bucket(BUCKET).list_objects(prefix="img/")
        assert len(listing.objects) == 2
        keys = [o.key for o in listing.objects]
        assert all(k.startswith("img/") for k in keys)


class TestObjectOperation:
    async def test_put_get_roundtrip(self, builder):
        obj = await builder.object(BUCKET, "r.txt").put(
            b"roundtrip", metadata={"ver": "1"}
        )
        assert obj.data == b"roundtrip"
        assert obj.metadata == {"ver": "1"}

        retrieved = await builder.object(BUCKET, "r.txt").get()
        assert retrieved.data == b"roundtrip"
        assert retrieved.metadata == {"ver": "1"}

    async def test_head(self, builder):
        await builder.object(BUCKET, "head.txt").put(b"data")
        obj = await builder.object(BUCKET, "head.txt").head()
        assert obj.data is None
        assert obj.size == 4

    async def test_delete(self, builder):
        await builder.object(BUCKET, "del.txt").put(b"x")
        await builder.object(BUCKET, "del.txt").delete()
        with pytest.raises(KeyError):
            await builder.object(BUCKET, "del.txt").get()

    async def test_copy_to(self, builder):
        await builder.object(BUCKET, "src.txt").put(b"source", metadata={"n": 1})
        copied = await builder.object(BUCKET, "src.txt").copy_to(BUCKET, "dst.txt")
        assert copied.key == "dst.txt"
        assert copied.metadata == {"n": 1}

    async def test_presigned_urls(self, builder):
        get_url = await builder.object(BUCKET, "p.txt").presigned_get(60)
        put_url = await builder.object(BUCKET, "p.txt").presigned_put(120)
        assert "fake://" in get_url
        assert "action=get" in get_url
        assert "action=put" in put_url


class TestPutSources:
    """Tests for put_from_data, put_from_path, and put_from_source."""

    async def test_put_shorthand_delegates_to_put_from_data(self, builder):
        obj = await builder.object(BUCKET, "blob.bin").put(b"blob data")
        assert obj.data == b"blob data"
        assert obj.size == 9

    async def test_put_from_data(self, builder):
        obj = await builder.object(BUCKET, "data.bin").put_from_data(
            b"raw bytes", metadata={"kind": "bytes"}
        )
        assert obj.data == b"raw bytes"
        assert obj.metadata == {"kind": "bytes"}

    async def test_put_from_path(self, builder, tmp_path):
        file_path = tmp_path / "test.txt"
        file_path.write_bytes(b"file content")

        obj = await builder.object(BUCKET, "from-file.txt").put_from_path(
            path=str(file_path), metadata={"source": "disk"}
        )
        assert obj.data == b"file content"
        assert obj.metadata == {"source": "disk"}

    async def test_put_from_source_bytesio(self, builder):
        import io
        source = io.BytesIO(b"stream content")

        obj = await builder.object(BUCKET, "from-source.bin").put_from_source(
            source=source, content_type="application/octet-stream"
        )
        assert obj.data == b"stream content"
        assert obj.content_type == "application/octet-stream"

    async def test_put_from_source_bytes(self, builder):
        obj = await builder.object(BUCKET, "from-bytes.bin").put_from_source(
            source=b"plain bytes as source"
        )
        assert obj.data == b"plain bytes as source"


class TestMultipartOperation:
    async def test_full_multipart_flow(self, builder):
        mp = builder.multipart(BUCKET, "big-file.dat")
        upload_id = await mp.create(metadata={"chunks": "2"})
        p1 = await mp.upload_part(upload_id, 1, b"part-A-")
        p2 = await mp.upload_part(upload_id, 2, b"part-B")
        obj = await mp.complete(upload_id, [p1, p2])
        assert obj.data == b"part-A-part-B"
        assert obj.metadata == {"chunks": "2"}

    async def test_abort_multipart(self, builder):
        mp = builder.multipart(BUCKET, "aborted.dat")
        upload_id = await mp.create()
        await mp.upload_part(upload_id, 1, b"data")
        await mp.abort(upload_id)
        listing = await builder.bucket(BUCKET).list_objects(prefix="aborted")
        assert len(listing.objects) == 0
