import os
import time
import pytest
from uuid import uuid4
from azure.identity import DefaultAzureCredential
from azure.storage.blob import BlobServiceClient

from aleph_blobstorage.adapters import AzureBlobAdaptor
from aleph_blobstorage.valueobjects import VOBlobURI


@pytest.fixture
def blob_client():
    credentials = DefaultAzureCredential()
    account_name = os.environ.get("TEST_AZURE_STORAGE_ACCOUNT")
    if not account_name:
        raise EnvironmentError(
            "Missing environment variable: TEST_AZURE_STORAGE_ACCOUNT. Set the storage account name for testing. "
        )
    account_url = f"https://{account_name}.blob.core.windows.net"
    return BlobServiceClient(account_url=account_url, credential=credentials)


@pytest.fixture
def storage(blob_client):
    container_name = os.environ.get("TEST_AZURE_STORAGE_CONTAINER")
    if not container_name:
        raise EnvironmentError(
            "Missing environment variable: TEST_AZUTEST_AZURE_STORAGE_CONTAINER. Set the container name for testing. "
        )
    return AzureBlobAdaptor(
        container_name="testazureblobstorage3",
        blob_client=blob_client,
    )


@pytest.fixture
def tmp_file(tmp_path):
    test_file = tmp_path / "test_file.py"
    test_file.write_text("This is a test blob.")
    return test_file


class TestPathHandling:
    def test_path_building(self, storage):
        uri = VOBlobURI(
            logical_path="folder", logical_name="file.txt", version="abc123"
        )
        path = storage._build_storage_path(uri)
        assert path == "folder/file.txt"


class TestUpload:
    def test_file_upload(self, storage, tmp_file):
        blob_uri = VOBlobURI(logical_path="folder1", logical_name="test.py")

        start = time.time()
        lifecycle = storage.upload_from_file(destination=blob_uri, source_path=tmp_file)
        end = time.time()
        elapsed = end - start
        print(f"⏱ Upload time: {elapsed:.4f} seconds")

        meta = lifecycle.versions[0]
        assert storage._check_azblob_version_exists_by_path(
            storage._build_storage_path(blob_uri), meta.uri.version
        )
        assert meta.uri.logical_path == blob_uri.logical_path
        assert meta.uri.logical_name == blob_uri.logical_name
        assert meta.size_bytes == tmp_file.stat().st_size

    def test_memory_upload(self, storage):
        blob_uri = VOBlobURI(logical_path="mem", logical_name="mem.txt")
        content = b"memory upload test"
        lifecycle = storage.upload_from_memory(destination=blob_uri, content=content)
        meta = lifecycle.versions[0]
        assert storage._check_azblob_version_exists_by_path(
            storage._build_storage_path(blob_uri), meta.uri.version
        )
        downloaded = storage.download_to_memory(blob_uri)
        assert downloaded == content

    def test_upload_with_tags(self, storage, tmp_path):
        test_file = tmp_path / "tagged.txt"
        test_file.write_text("tagged upload")
        blob_uri = VOBlobURI(logical_path="meta", logical_name="tagged.txt")
        tags = {"project": "aleph", "env": "test"}
        lifecycle = storage.upload_from_file(
            destination=blob_uri, source_path=test_file, tags=tags
        )
        meta = lifecycle.versions[0].tags
        assert meta.get("project") == "aleph"
        assert meta.get("env") == "test"


class TestDownload:
    def test_download_to_memory(self, storage, tmp_path):
        test_file = tmp_path / "mem.txt"
        test_file.write_text("download test")
        blob_uri = VOBlobURI(logical_path="dl", logical_name="mem.txt")

        start = time.time()
        storage.upload_from_file(destination=blob_uri, source_path=test_file)
        end = time.time()
        elapsed = end - start
        print(f"⏱ Download time: {elapsed:.4f} seconds")

        data = storage.download_to_memory(blob_uri, max_size_bytes=100)
        assert data == b"download test"

    def test_download_size_limit(self, storage, tmp_path):
        test_file = tmp_path / "big.txt"
        test_file.write_text("x" * 200)
        blob_uri = VOBlobURI(logical_path="dl", logical_name="big.txt")
        storage.upload_from_file(destination=blob_uri, source_path=test_file)
        with pytest.raises(ValueError):
            storage.download_to_memory(blob_uri, max_size_bytes=10)

    def test_download_missing(self, storage):
        blob_uri = VOBlobURI(logical_path="none", logical_name="missing.txt")
        with pytest.raises(FileNotFoundError):
            storage.download_to_memory(blob_uri, max_size_bytes=100)

    def test_download_to_file(self, storage, tmp_path):
        # Upload a file
        test_file = tmp_path / "file_to_download.txt"
        content = "download to file test"
        test_file.write_text(content)
        blob_uri = VOBlobURI(logical_path="dl", logical_name="file_to_download.txt")
        storage.upload_from_file(destination=blob_uri, source_path=test_file)

        # Download to a new local path
        download_path = tmp_path / "downloaded.txt"
        result_path = storage.download_to_file(
            source=blob_uri, destination_path=download_path
        )
        assert result_path.exists()
        assert result_path.read_text() == content

    def test_download_as_stream(self, storage, tmp_path):
        # Upload a file
        test_file = tmp_path / "stream.txt"
        content = "streamed content for azure test"
        test_file.write_text(content)
        blob_uri = VOBlobURI(logical_path="dl", logical_name="stream.txt")
        storage.upload_from_file(destination=blob_uri, source_path=test_file)

        # Download as stream
        chunks = list(storage.download_as_stream(blob_uri, chunk_size=8))
        reconstructed = b"".join(chunks)
        assert reconstructed == content.encode()


class TestVersioning:
    def test_version_resolution(self, storage, tmp_path):
        test_file = tmp_path / "ver.txt"
        test_file.write_text("v1")
        uri = VOBlobURI(logical_path="ver", logical_name="ver.txt")
        lifecycle = storage.upload_from_file(destination=uri, source_path=test_file)
        latest_uri = VOBlobURI(
            logical_path="ver", logical_name="ver.txt", version="latest"
        )
        resolved = storage._resolve_versioned_uri(latest_uri)
        assert resolved.version == lifecycle.versions[0].uri.version

    def test_version_listing(self, storage, tmp_path):
        test_file = tmp_path / "multi.txt"
        test_file.write_text("v1")
        uri = VOBlobURI(logical_path="ver", logical_name="multi.txt")
        storage.upload_from_file(destination=uri, source_path=test_file)
        test_file.write_text("v2")
        storage.upload_from_file(destination=uri, source_path=test_file)
        lifecycle = storage.get_lifecycle_entity(uri)
        assert len(lifecycle.versions) > 1

    def test_version_tags(self, storage, tmp_path):
        test_file = tmp_path / "tagver.txt"
        uri = VOBlobURI(logical_path="ver", logical_name="tagversion.txt")

        # First version with tag1
        test_file.write_text("v1")
        tags_v1 = {"tag": "v1"}
        storage.upload_from_file(destination=uri, source_path=test_file, tags=tags_v1)

        # Second version with tag2
        test_file.write_text("v2")
        tags_v2 = {"tag": "v2"}
        storage.upload_from_file(destination=uri, source_path=test_file, tags=tags_v2)

        lifecycle = storage.get_lifecycle_entity(uri)
        assert len(lifecycle.versions) >= 2
        assert lifecycle.versions[0].tags.get("tag") == "v2"
        assert lifecycle.versions[1].tags.get("tag") == "v1"


class TestDelete:
    def test_delete_blob(self, storage, tmp_path):
        test_file = tmp_path / "delete.txt"
        test_file.write_text("delete me")
        uri = VOBlobURI(logical_path="del", logical_name="del.txt")
        storage.upload_from_file(destination=uri, source_path=test_file)
        storage.delete(uri)
        assert not storage._check_blob_exists(uri)
        with pytest.raises(FileNotFoundError):
            storage.get_lifecycle_entity(uri)

    def test_delete_missing(self, storage):
        uri = VOBlobURI(logical_path="none", logical_name="missing.txt")
        with pytest.raises(FileNotFoundError):
            storage.delete(uri)


class TestList:
    def test_list_blobs(self, storage, tmp_path):
        uris = []
        for i in range(3):
            test_file = tmp_path / f"test{1}.txt"
            test_file.write_text(f"list {i}")
            uri = VOBlobURI(logical_path="list", logical_name=f"list{i}.txt")
            lifecycle = storage.upload_from_file(destination=uri, source_path=test_file)
            uris.append(lifecycle.versions[0].uri)

        blobs_retrieved = storage.list_blobs(path="list")
        retrieved_versions = [b.version for b in blobs_retrieved]
        for uri in uris:
            assert uri.version in retrieved_versions

    def test_list_blobs_with_tags(self, storage, tmp_path):
        test_file = tmp_path / "withtag.txt"
        test_file.write_text("This has tags")
        uri = VOBlobURI(logical_path="list", logical_name="withtag.txt")
        tags = {"env": "test"}
        storage.upload_from_file(destination=uri, source_path=test_file, tags=tags)

        test_file = tmp_path / "notag.txt"
        test_file.write_text("This does not have tags")
        uri = VOBlobURI(logical_path="list", logical_name="notag.txt")
        storage.upload_from_file(destination=uri, source_path=test_file)

        blobs_retrieved = storage.list_blobs(path="list", tags=tags)
        assert len(blobs_retrieved) == 1


class TestRestore:
    def test_restore_blob(self, storage, tmp_path):
        test_file = tmp_path / "restore.txt"
        content1 = str(uuid4()) * 2
        test_file.write_text(content1)
        uri = VOBlobURI(logical_path="restore", logical_name="restore14.txt")
        storage.upload_from_file(destination=uri, source_path=test_file)

        content2 = str(uuid4())
        test_file.write_text(content2)
        storage.upload_from_file(destination=uri, source_path=test_file)

        storage.delete(uri)

        lifecycle_entity = storage.restore(uri)
        assert len(lifecycle_entity.versions) > 0
        data = storage.download_to_memory(uri)
        assert data == content2.encode()
