"""
Tests for pre-signed URL functionality across all storage backends.

TDD Phase 1: RED - Write tests first, then implement to pass.
"""

import shutil
import tempfile
from pathlib import Path
from unittest.mock import MagicMock, patch

import pytest


@pytest.fixture
def temp_storage_dir() -> Path:
    """Create a temporary directory for storage tests."""
    temp_dir = Path(tempfile.mkdtemp())
    yield temp_dir
    shutil.rmtree(temp_dir, ignore_errors=True)


@pytest.fixture
def sample_file(temp_storage_dir: Path) -> Path:
    """Create a sample file for testing."""
    file_path = temp_storage_dir / "sample.txt"
    file_path.write_text("Hello, World!")
    return file_path


class TestStorageBackendInterfacePresignedUrl:
    """Tests for get_presigned_url in StorageBackend interface."""

    def test_subclass_must_implement_get_presigned_url(self) -> None:
        """Test that subclass must implement get_presigned_url method."""
        from shared.storage.base import StorageBackend

        class IncompleteBackend(StorageBackend):
            def upload(
                self, local_path: Path, remote_path: str, overwrite: bool = False
            ) -> str:
                return remote_path

            def download(self, remote_path: str, local_path: Path) -> Path:
                return local_path

            def exists(self, remote_path: str) -> bool:
                return False

            def list_files(self, prefix: str) -> list[str]:
                return []

            def delete(self, remote_path: str) -> bool:
                return True

            def get_url(self, remote_path: str) -> str:
                return ""

        with pytest.raises(TypeError):
            IncompleteBackend()  # type: ignore

    def test_valid_subclass_with_get_presigned_url_can_be_instantiated(self) -> None:
        """Test that a complete subclass with get_presigned_url can be instantiated."""
        from shared.storage.base import StorageBackend

        class CompleteBackend(StorageBackend):
            def upload(
                self, local_path: Path, remote_path: str, overwrite: bool = False
            ) -> str:
                return remote_path

            def download(self, remote_path: str, local_path: Path) -> Path:
                return local_path

            def exists(self, remote_path: str) -> bool:
                return False

            def list_files(self, prefix: str) -> list[str]:
                return []

            def delete(self, remote_path: str) -> bool:
                return True

            def get_url(self, remote_path: str) -> str:
                return ""

            def get_presigned_url(
                self, remote_path: str, expires_in_seconds: int = 3600
            ) -> str:
                return f"https://example.com/{remote_path}?token=abc"

        backend = CompleteBackend()
        assert isinstance(backend, StorageBackend)


class TestLocalStorageBackendPresignedUrl:
    """Tests for LocalStorageBackend.get_presigned_url method."""

    def test_get_presigned_url_returns_file_uri(
        self, temp_storage_dir: Path, sample_file: Path
    ) -> None:
        """Test get_presigned_url returns file:// URI for existing file."""
        from shared.storage.local import LocalStorageBackend

        storage_dir = temp_storage_dir / "storage"
        backend = LocalStorageBackend(base_path=storage_dir)
        backend.upload(sample_file, "sample.txt")

        url = backend.get_presigned_url("sample.txt")

        assert url.startswith("file://")
        assert "sample.txt" in url

    def test_get_presigned_url_with_custom_expiry(
        self, temp_storage_dir: Path, sample_file: Path
    ) -> None:
        """Test get_presigned_url accepts expires_in_seconds parameter."""
        from shared.storage.local import LocalStorageBackend

        storage_dir = temp_storage_dir / "storage"
        backend = LocalStorageBackend(base_path=storage_dir)
        backend.upload(sample_file, "sample.txt")

        # For local storage, expiry is ignored but should not raise error
        url = backend.get_presigned_url("sample.txt", expires_in_seconds=7200)

        assert url.startswith("file://")

    def test_get_presigned_url_nonexistent_file_raises(
        self, temp_storage_dir: Path
    ) -> None:
        """Test get_presigned_url raises FileNotFoundStorageError for missing file."""
        from shared.storage.base import FileNotFoundStorageError
        from shared.storage.local import LocalStorageBackend

        backend = LocalStorageBackend(base_path=temp_storage_dir)

        with pytest.raises(FileNotFoundStorageError):
            backend.get_presigned_url("nonexistent.txt")

    def test_get_presigned_url_path_traversal_blocked(
        self, temp_storage_dir: Path
    ) -> None:
        """Test that path traversal in get_presigned_url is blocked."""
        from shared.storage.base import StorageError
        from shared.storage.local import LocalStorageBackend

        backend = LocalStorageBackend(base_path=temp_storage_dir)

        with pytest.raises(StorageError, match="Path traversal not allowed"):
            backend.get_presigned_url("../escape.txt")

    def test_get_presigned_url_nested_path(
        self, temp_storage_dir: Path, sample_file: Path
    ) -> None:
        """Test get_presigned_url works with nested paths."""
        from shared.storage.local import LocalStorageBackend

        storage_dir = temp_storage_dir / "storage"
        backend = LocalStorageBackend(base_path=storage_dir)
        backend.upload(sample_file, "a/b/c/sample.txt")

        url = backend.get_presigned_url("a/b/c/sample.txt")

        assert url.startswith("file://")
        assert "sample.txt" in url


class TestAzureBlobStorageBackendPresignedUrl:
    """Tests for AzureBlobStorageBackend.get_presigned_url method."""

    @patch("shared.storage.azure.BlobServiceClient")
    def test_get_presigned_url_generates_sas_url(
        self, mock_blob_service_class: MagicMock
    ) -> None:
        """Test get_presigned_url generates URL with SAS token."""
        from shared.storage.azure import AzureBlobStorageBackend

        # Setup mocks
        mock_blob_service = MagicMock()
        mock_blob_service.account_name = "testaccount"
        mock_blob_service_class.from_connection_string.return_value = mock_blob_service

        mock_container = MagicMock()
        mock_container.exists.return_value = True
        mock_blob_service.get_container_client.return_value = mock_container

        mock_blob_client = MagicMock()
        mock_blob_client.exists.return_value = True
        mock_blob_client.url = "https://testaccount.blob.core.windows.net/container/test.txt"
        mock_container.get_blob_client.return_value = mock_blob_client

        backend = AzureBlobStorageBackend(
            connection_string="DefaultEndpointsProtocol=https;AccountName=testaccount;AccountKey=testkey==;EndpointSuffix=core.windows.net",
            container_name="container",
        )

        with patch("shared.storage.azure.generate_blob_sas") as mock_generate_sas:
            mock_generate_sas.return_value = "sv=2021-06-08&sr=b&sig=abc123"

            url = backend.get_presigned_url("test.txt", expires_in_seconds=3600)

            assert "https://testaccount.blob.core.windows.net" in url
            assert "sv=2021-06-08" in url or "test.txt" in url

    @patch("shared.storage.azure.BlobServiceClient")
    def test_get_presigned_url_nonexistent_blob_raises(
        self, mock_blob_service_class: MagicMock
    ) -> None:
        """Test get_presigned_url raises for nonexistent blob."""
        from shared.storage.base import FileNotFoundStorageError
        from shared.storage.azure import AzureBlobStorageBackend

        mock_blob_service = MagicMock()
        mock_blob_service_class.from_connection_string.return_value = mock_blob_service

        mock_container = MagicMock()
        mock_container.exists.return_value = True
        mock_blob_service.get_container_client.return_value = mock_container

        mock_blob_client = MagicMock()
        mock_blob_client.exists.return_value = False
        mock_container.get_blob_client.return_value = mock_blob_client

        backend = AzureBlobStorageBackend(
            connection_string="DefaultEndpointsProtocol=https;AccountName=test;AccountKey=key==;EndpointSuffix=core.windows.net",
            container_name="container",
        )

        with pytest.raises(FileNotFoundStorageError):
            backend.get_presigned_url("nonexistent.txt")

    @patch("shared.storage.azure.BlobServiceClient")
    def test_get_presigned_url_uses_custom_expiry(
        self, mock_blob_service_class: MagicMock
    ) -> None:
        """Test get_presigned_url uses custom expiry time."""
        from shared.storage.azure import AzureBlobStorageBackend

        mock_blob_service = MagicMock()
        mock_blob_service.account_name = "testaccount"
        mock_blob_service_class.from_connection_string.return_value = mock_blob_service

        mock_container = MagicMock()
        mock_container.exists.return_value = True
        mock_blob_service.get_container_client.return_value = mock_container

        mock_blob_client = MagicMock()
        mock_blob_client.exists.return_value = True
        mock_blob_client.url = "https://testaccount.blob.core.windows.net/container/test.txt"
        mock_container.get_blob_client.return_value = mock_blob_client

        backend = AzureBlobStorageBackend(
            connection_string="DefaultEndpointsProtocol=https;AccountName=testaccount;AccountKey=testkey==;EndpointSuffix=core.windows.net",
            container_name="container",
        )

        with patch("shared.storage.azure.generate_blob_sas") as mock_generate_sas:
            mock_generate_sas.return_value = "sv=2021-06-08&sr=b&sig=abc123"

            backend.get_presigned_url("test.txt", expires_in_seconds=7200)

            # Verify generate_blob_sas was called (expiry is part of the call)
            mock_generate_sas.assert_called_once()
