"""
Unit Tests für DSMS Gateway
Tests für alle API-Endpoints und Hilfsfunktionen
"""

import pytest
import hashlib
import json
from unittest.mock import AsyncMock, patch, MagicMock
from fastapi.testclient import TestClient
from httpx import Response

# Import der App
from main import app, DocumentMetadata, StoredDocument, DocumentList


# Test Client
client = TestClient(app)


# ==================== Fixtures ====================

@pytest.fixture
def valid_auth_header():
    """Gültiger Authorization Header für Tests"""
    return {"Authorization": "Bearer test-token-12345"}


@pytest.fixture
def sample_document_metadata():
    """Beispiel-Metadaten für Tests"""
    return DocumentMetadata(
        document_type="legal_document",
        document_id="doc-123",
        version="1.0",
        language="de",
        created_at="2024-01-01T00:00:00",
        checksum="abc123",
        encrypted=False
    )


@pytest.fixture
def mock_ipfs_response():
    """Mock-Antwort von IPFS add"""
    return {
        "Hash": "QmTest1234567890abcdef",
        "Size": "1024"
    }


# ==================== Health Check Tests ====================

class TestHealthCheck:
    """Tests für den Health Check Endpoint"""

    def test_health_check_ipfs_connected(self):
        """Test: Health Check wenn IPFS verbunden ist"""
        with patch("routers.node.httpx.AsyncClient") as mock_client:
            mock_instance = AsyncMock()
            mock_instance.post.return_value = MagicMock(status_code=200)
            mock_client.return_value.__aenter__.return_value = mock_instance

            response = client.get("/health")

            assert response.status_code == 200
            data = response.json()
            assert "status" in data
            assert "ipfs_connected" in data
            assert "timestamp" in data

    def test_health_check_ipfs_disconnected(self):
        """Test: Health Check wenn IPFS nicht erreichbar"""
        with patch("routers.node.httpx.AsyncClient") as mock_client:
            mock_instance = AsyncMock()
            mock_instance.post.side_effect = Exception("Connection failed")
            mock_client.return_value.__aenter__.return_value = mock_instance

            response = client.get("/health")

            assert response.status_code == 200
            data = response.json()
            assert data["status"] == "degraded"
            assert data["ipfs_connected"] is False


# ==================== Authorization Tests ====================

class TestAuthorization:
    """Tests für die Autorisierung"""

    def test_documents_endpoint_without_auth_returns_401(self):
        """Test: Dokument-Endpoint ohne Auth gibt 401 zurück"""
        response = client.get("/api/v1/documents")
        assert response.status_code == 401

    def test_documents_endpoint_with_invalid_token_returns_401(self):
        """Test: Ungültiges Token-Format gibt 401 zurück"""
        response = client.get(
            "/api/v1/documents",
            headers={"Authorization": "InvalidFormat"}
        )
        assert response.status_code == 401

    def test_documents_endpoint_with_valid_token_format(self, valid_auth_header):
        """Test: Gültiges Token-Format wird akzeptiert"""
        with patch("routers.documents.ipfs_pin_ls", new_callable=AsyncMock) as mock_pin_ls:
            mock_pin_ls.return_value = []

            response = client.get(
                "/api/v1/documents",
                headers=valid_auth_header
            )

            assert response.status_code == 200


# ==================== Document Storage Tests ====================

class TestDocumentStorage:
    """Tests für das Speichern von Dokumenten"""

    def test_store_document_success(self, valid_auth_header, mock_ipfs_response):
        """Test: Dokument erfolgreich speichern"""
        with patch("routers.documents.ipfs_add", new_callable=AsyncMock) as mock_add:
            mock_add.return_value = mock_ipfs_response

            test_content = b"Test document content"

            response = client.post(
                "/api/v1/documents",
                headers=valid_auth_header,
                files={"file": ("test.txt", test_content, "text/plain")},
                data={
                    "document_type": "legal_document",
                    "document_id": "doc-123",
                    "version": "1.0",
                    "language": "de"
                }
            )

            assert response.status_code == 200
            data = response.json()
            assert "cid" in data
            assert data["cid"] == "QmTest1234567890abcdef"
            assert "metadata" in data
            assert "gateway_url" in data

    def test_store_document_calculates_checksum(self, valid_auth_header, mock_ipfs_response):
        """Test: Checksum wird korrekt berechnet"""
        with patch("routers.documents.ipfs_add", new_callable=AsyncMock) as mock_add:
            mock_add.return_value = mock_ipfs_response

            test_content = b"Test content for checksum"
            expected_checksum = hashlib.sha256(test_content).hexdigest()

            response = client.post(
                "/api/v1/documents",
                headers=valid_auth_header,
                files={"file": ("test.txt", test_content, "text/plain")}
            )

            assert response.status_code == 200
            data = response.json()
            assert data["metadata"]["checksum"] == expected_checksum

    def test_store_document_without_file_returns_422(self, valid_auth_header):
        """Test: Fehlende Datei gibt 422 zurück"""
        response = client.post(
            "/api/v1/documents",
            headers=valid_auth_header
        )

        assert response.status_code == 422


# ==================== Document Retrieval Tests ====================

class TestDocumentRetrieval:
    """Tests für das Abrufen von Dokumenten"""

    def test_get_document_success(self, valid_auth_header):
        """Test: Dokument erfolgreich abrufen"""
        test_content = b"Original content"
        package = {
            "metadata": {
                "document_type": "legal_document",
                "checksum": hashlib.sha256(test_content).hexdigest()
            },
            "content_base64": test_content.hex(),
            "filename": "test.txt"
        }

        with patch("routers.documents.ipfs_cat", new_callable=AsyncMock) as mock_cat:
            mock_cat.return_value = json.dumps(package).encode()

            response = client.get(
                "/api/v1/documents/QmTestCid123",
                headers=valid_auth_header
            )

            assert response.status_code == 200
            assert response.content == test_content

    def test_get_document_not_found(self, valid_auth_header):
        """Test: Nicht existierendes Dokument gibt 404 zurück"""
        with patch("routers.documents.ipfs_cat", new_callable=AsyncMock) as mock_cat:
            from fastapi import HTTPException
            mock_cat.side_effect = HTTPException(status_code=404, detail="Not found")

            response = client.get(
                "/api/v1/documents/QmNonExistent",
                headers=valid_auth_header
            )

            assert response.status_code == 404

    def test_get_document_metadata_success(self, valid_auth_header):
        """Test: Dokument-Metadaten abrufen"""
        test_content = b"Content"
        package = {
            "metadata": {
                "document_type": "legal_document",
                "document_id": "doc-123",
                "version": "1.0"
            },
            "content_base64": test_content.hex(),
            "filename": "test.txt"
        }

        with patch("routers.documents.ipfs_cat", new_callable=AsyncMock) as mock_cat:
            mock_cat.return_value = json.dumps(package).encode()

            response = client.get(
                "/api/v1/documents/QmTestCid123/metadata",
                headers=valid_auth_header
            )

            assert response.status_code == 200
            data = response.json()
            assert data["cid"] == "QmTestCid123"
            assert data["metadata"]["document_type"] == "legal_document"


# ==================== Document List Tests ====================

class TestDocumentList:
    """Tests für das Auflisten von Dokumenten"""

    def test_list_documents_empty(self, valid_auth_header):
        """Test: Leere Dokumentenliste"""
        with patch("routers.documents.ipfs_pin_ls", new_callable=AsyncMock) as mock_pin_ls:
            mock_pin_ls.return_value = []

            response = client.get(
                "/api/v1/documents",
                headers=valid_auth_header
            )

            assert response.status_code == 200
            data = response.json()
            assert data["documents"] == []
            assert data["total"] == 0

    def test_list_documents_with_items(self, valid_auth_header):
        """Test: Dokumentenliste mit Einträgen"""
        package = {
            "metadata": {"document_type": "legal_document"},
            "content_base64": "68656c6c6f",
            "filename": "test.txt"
        }

        with patch("routers.documents.ipfs_pin_ls", new_callable=AsyncMock) as mock_pin_ls:
            mock_pin_ls.return_value = ["QmCid1", "QmCid2"]

            with patch("routers.documents.ipfs_cat", new_callable=AsyncMock) as mock_cat:
                mock_cat.return_value = json.dumps(package).encode()

                response = client.get(
                    "/api/v1/documents",
                    headers=valid_auth_header
                )

                assert response.status_code == 200
                data = response.json()
                assert data["total"] == 2


# ==================== Document Deletion Tests ====================

class TestDocumentDeletion:
    """Tests für das Löschen von Dokumenten"""

    def test_unpin_document_success(self, valid_auth_header):
        """Test: Dokument erfolgreich unpinnen"""
        with patch("routers.documents.httpx.AsyncClient") as mock_client:
            mock_instance = AsyncMock()
            mock_instance.post.return_value = MagicMock(status_code=200)
            mock_client.return_value.__aenter__.return_value = mock_instance

            response = client.delete(
                "/api/v1/documents/QmTestCid123",
                headers=valid_auth_header
            )

            assert response.status_code == 200
            data = response.json()
            assert data["status"] == "unpinned"
            assert data["cid"] == "QmTestCid123"

    def test_unpin_document_not_found(self, valid_auth_header):
        """Test: Nicht existierendes Dokument unpinnen"""
        with patch("routers.documents.httpx.AsyncClient") as mock_client:
            mock_instance = AsyncMock()
            mock_instance.post.return_value = MagicMock(status_code=404)
            mock_client.return_value.__aenter__.return_value = mock_instance

            response = client.delete(
                "/api/v1/documents/QmNonExistent",
                headers=valid_auth_header
            )

            assert response.status_code == 404


# ==================== Legal Document Archive Tests ====================

class TestLegalDocumentArchive:
    """Tests für die Legal Document Archivierung"""

    def test_archive_legal_document_success(self, valid_auth_header, mock_ipfs_response):
        """Test: Legal Document erfolgreich archivieren"""
        with patch("routers.documents.ipfs_add", new_callable=AsyncMock) as mock_add:
            mock_add.return_value = mock_ipfs_response

            response = client.post(
                "/api/v1/legal-documents/archive",
                headers=valid_auth_header,
                params={
                    "document_id": "privacy-policy",
                    "version": "2.0",
                    "content": "<h1>Datenschutzerklärung</h1>",
                    "language": "de"
                }
            )

            assert response.status_code == 200
            data = response.json()
            assert "cid" in data
            assert data["document_id"] == "privacy-policy"
            assert data["version"] == "2.0"
            assert "checksum" in data
            assert "archived_at" in data

    def test_archive_legal_document_calculates_checksum(self, valid_auth_header, mock_ipfs_response):
        """Test: Checksum für HTML-Inhalt korrekt berechnet"""
        content = "<h1>Test Content</h1>"
        expected_checksum = hashlib.sha256(content.encode('utf-8')).hexdigest()

        with patch("routers.documents.ipfs_add", new_callable=AsyncMock) as mock_add:
            mock_add.return_value = mock_ipfs_response

            response = client.post(
                "/api/v1/legal-documents/archive",
                headers=valid_auth_header,
                params={
                    "document_id": "terms",
                    "version": "1.0",
                    "content": content
                }
            )

            assert response.status_code == 200
            data = response.json()
            assert data["checksum"] == expected_checksum


# ==================== Document Verification Tests ====================

class TestDocumentVerification:
    """Tests für die Dokumenten-Verifizierung"""

    def test_verify_document_integrity_valid(self):
        """Test: Dokument mit gültiger Integrität"""
        content = "Test content"
        checksum = hashlib.sha256(content.encode('utf-8')).hexdigest()

        package = {
            "metadata": {
                "document_type": "legal_document",
                "checksum": checksum
            },
            "content": content
        }

        with patch("routers.node.ipfs_cat", new_callable=AsyncMock) as mock_cat:
            mock_cat.return_value = json.dumps(package).encode()

            response = client.get("/api/v1/verify/QmTestCid123")

            assert response.status_code == 200
            data = response.json()
            assert data["exists"] is True
            assert data["integrity_valid"] is True
            assert data["stored_checksum"] == checksum
            assert data["calculated_checksum"] == checksum

    def test_verify_document_integrity_invalid(self):
        """Test: Dokument mit ungültiger Integrität (manipuliert)"""
        package = {
            "metadata": {
                "document_type": "legal_document",
                "checksum": "fake_checksum_12345"
            },
            "content": "Actual content"
        }

        with patch("routers.node.ipfs_cat", new_callable=AsyncMock) as mock_cat:
            mock_cat.return_value = json.dumps(package).encode()

            response = client.get("/api/v1/verify/QmTestCid123")

            assert response.status_code == 200
            data = response.json()
            assert data["exists"] is True
            assert data["integrity_valid"] is False

    def test_verify_document_not_found(self):
        """Test: Nicht existierendes Dokument verifizieren"""
        with patch("routers.node.ipfs_cat", new_callable=AsyncMock) as mock_cat:
            mock_cat.side_effect = Exception("Not found")

            response = client.get("/api/v1/verify/QmNonExistent")

            assert response.status_code == 200
            data = response.json()
            assert data["exists"] is False
            assert "error" in data

    def test_verify_document_public_access(self):
        """Test: Verifizierung ist öffentlich zugänglich (keine Auth)"""
        package = {
            "metadata": {"checksum": "abc"},
            "content": "test"
        }

        with patch("routers.node.ipfs_cat", new_callable=AsyncMock) as mock_cat:
            mock_cat.return_value = json.dumps(package).encode()

            # Kein Authorization Header!
            response = client.get("/api/v1/verify/QmTestCid123")

            assert response.status_code == 200


# ==================== Node Info Tests ====================

class TestNodeInfo:
    """Tests für Node-Informationen"""

    def test_get_node_info_success(self):
        """Test: Node-Informationen abrufen"""
        id_response = {
            "ID": "QmNodeId12345",
            "ProtocolVersion": "ipfs/0.1.0",
            "AgentVersion": "kubo/0.24.0",
            "Addresses": ["/ip4/127.0.0.1/tcp/4001"]
        }
        stat_response = {
            "RepoSize": 1048576,
            "StorageMax": 10737418240,
            "NumObjects": 42
        }

        with patch("routers.node.httpx.AsyncClient") as mock_client:
            mock_instance = AsyncMock()

            async def mock_post(url, **kwargs):
                mock_resp = MagicMock()
                if "id" in url:
                    mock_resp.status_code = 200
                    mock_resp.json.return_value = id_response
                elif "stat" in url:
                    mock_resp.status_code = 200
                    mock_resp.json.return_value = stat_response
                return mock_resp

            mock_instance.post = mock_post
            mock_client.return_value.__aenter__.return_value = mock_instance

            response = client.get("/api/v1/node/info")

            assert response.status_code == 200
            data = response.json()
            assert data["node_id"] == "QmNodeId12345"
            assert data["num_objects"] == 42

    def test_get_node_info_public_access(self):
        """Test: Node-Info ist öffentlich zugänglich"""
        with patch("routers.node.httpx.AsyncClient") as mock_client:
            mock_instance = AsyncMock()
            mock_instance.post.return_value = MagicMock(
                status_code=200,
                json=lambda: {}
            )
            mock_client.return_value.__aenter__.return_value = mock_instance

            # Kein Authorization Header!
            response = client.get("/api/v1/node/info")

            assert response.status_code == 200


# ==================== Model Tests ====================

class TestModels:
    """Tests für Pydantic Models"""

    def test_document_metadata_defaults(self):
        """Test: DocumentMetadata Default-Werte"""
        metadata = DocumentMetadata(document_type="test")

        assert metadata.document_type == "test"
        assert metadata.document_id is None
        assert metadata.version is None
        assert metadata.language == "de"
        assert metadata.encrypted is False

    def test_document_metadata_all_fields(self):
        """Test: DocumentMetadata mit allen Feldern"""
        metadata = DocumentMetadata(
            document_type="legal_document",
            document_id="doc-123",
            version="1.0",
            language="en",
            created_at="2024-01-01T00:00:00",
            checksum="abc123",
            encrypted=True
        )

        assert metadata.document_type == "legal_document"
        assert metadata.document_id == "doc-123"
        assert metadata.version == "1.0"
        assert metadata.language == "en"
        assert metadata.encrypted is True

    def test_stored_document_model(self, sample_document_metadata):
        """Test: StoredDocument Model"""
        stored = StoredDocument(
            cid="QmTest123",
            size=1024,
            metadata=sample_document_metadata,
            gateway_url="http://localhost:8080/ipfs/QmTest123",
            timestamp="2024-01-01T00:00:00"
        )

        assert stored.cid == "QmTest123"
        assert stored.size == 1024
        assert stored.metadata.document_type == "legal_document"

    def test_document_list_model(self):
        """Test: DocumentList Model"""
        doc_list = DocumentList(
            documents=[{"cid": "Qm1"}, {"cid": "Qm2"}],
            total=2
        )

        assert doc_list.total == 2
        assert len(doc_list.documents) == 2


# ==================== Integration Tests ====================

class TestIntegration:
    """Integration Tests (erfordern laufenden IPFS Node)"""

    @pytest.mark.skip(reason="Erfordert laufenden IPFS Node")
    def test_full_document_lifecycle(self, valid_auth_header):
        """Integration Test: Vollständiger Dokument-Lebenszyklus"""
        # 1. Dokument speichern
        response = client.post(
            "/api/v1/documents",
            headers=valid_auth_header,
            files={"file": ("test.txt", b"Test content", "text/plain")}
        )
        assert response.status_code == 200
        cid = response.json()["cid"]

        # 2. Dokument abrufen
        response = client.get(
            f"/api/v1/documents/{cid}",
            headers=valid_auth_header
        )
        assert response.status_code == 200

        # 3. Verifizieren
        response = client.get(f"/api/v1/verify/{cid}")
        assert response.status_code == 200
        assert response.json()["integrity_valid"] is True

        # 4. Unpinnen
        response = client.delete(
            f"/api/v1/documents/{cid}",
            headers=valid_auth_header
        )
        assert response.status_code == 200


# ==================== Run Tests ====================

if __name__ == "__main__":
    pytest.main([__file__, "-v"])
