"""
Configuration Backup and Restore Integration Tests

Tests for configuration backup creation, restoration, listing, and deletion
Tests the complete backup/restore workflow and error handling
"""

import json
from datetime import datetime
from pathlib import Path

import pytest
from httpx import ASGITransport, AsyncClient

from src.server.fastapi_app import app
from src.server.services.config_service import get_config_service


@pytest.fixture
async def authenticated_client():
    """Create an authenticated test client."""
    transport = ASGITransport(app=app)
    async with AsyncClient(transport=transport, base_url="http://test") as ac:
        # Login to get token
        login_response = await ac.post(
            "/api/auth/login",
            json={"password": "TestPass123!"}
        )
        
        if login_response.status_code == 200:
            token_data = login_response.json()
            token = token_data.get("access_token")
            
            # Set authorization header
            ac.headers["Authorization"] = f"Bearer {token}"
        
        yield ac


class TestBackupCreation:
    """Tests for creating configuration backups."""

    async def test_create_backup_endpoint_exists(self, authenticated_client):
        """Test that the backup creation endpoint exists."""
        response = await authenticated_client.post("/api/config/backups")
        
        # Should not return 404
        assert response.status_code != 404

    async def test_create_backup_with_default_name(self, authenticated_client):
        """Test creating a backup with auto-generated timestamp name."""
        response = await authenticated_client.post("/api/config/backups")
        
        assert response.status_code in [200, 201]
        
        if response.status_code in [200, 201]:
            data = response.json()
            assert "name" in data
            assert "message" in data
            
            # Name should contain timestamp
            backup_name = data["name"]
            assert len(backup_name) > 0

    async def test_create_backup_with_custom_name(self, authenticated_client):
        """Test creating a backup with a custom name."""
        custom_name = "test_backup"
        
        response = await authenticated_client.post(
            "/api/config/backups",
            params={"name": custom_name}
        )
        
        if response.status_code in [200, 201]:
            data = response.json()
            assert custom_name in data["name"]

    async def test_create_backup_requires_authentication(self, authenticated_client):
        """Test that backup creation requires authentication."""
        # Create unauthenticated client
        transport = ASGITransport(app=app)
        async with AsyncClient(transport=transport, base_url="http://test") as client:
            response = await client.post("/api/config/backups")
            
            # Should require authentication
            assert response.status_code == 401

    async def test_backup_file_created_on_disk(self, authenticated_client):
        """Test that backup file is actually created."""
        response = await authenticated_client.post("/api/config/backups")
        
        if response.status_code in [200, 201]:
            data = response.json()
            backup_name = data["name"]
            
            # Verify file exists
            config_service = get_config_service()
            backup_dir = config_service.backup_dir
            backup_file = backup_dir / backup_name
            
            assert backup_file.exists()

    async def test_backup_contains_valid_json(self, authenticated_client):
        """Test that backup file contains valid JSON configuration."""
        response = await authenticated_client.post("/api/config/backups")
        
        if response.status_code in [200, 201]:
            data = response.json()
            backup_name = data["name"]
            
            # Read backup file
            config_service = get_config_service()
            backup_dir = config_service.backup_dir
            backup_file = backup_dir / backup_name
            
            if backup_file.exists():
                with open(backup_file, 'r') as f:
                    backup_data = json.load(f)
                
                # Should have expected fields
                assert isinstance(backup_data, dict)

    async def test_multiple_backups_can_be_created(self, authenticated_client):
        """Test that multiple backups can be created."""
        # Create first backup
        response1 = await authenticated_client.post("/api/config/backups")
        assert response1.status_code in [200, 201]
        
        # Wait a moment to ensure different timestamps (backup names use seconds)
        import asyncio
        await asyncio.sleep(1.1)
        
        # Create second backup
        response2 = await authenticated_client.post("/api/config/backups")
        assert response2.status_code in [200, 201]
        
        if response1.status_code in [200, 201] and response2.status_code in [200, 201]:
            data1 = response1.json()
            data2 = response2.json()
            
            # Names should be different
            assert data1["name"] != data2["name"]


class TestBackupListing:
    """Tests for listing configuration backups."""

    async def test_list_backups_endpoint_exists(self, authenticated_client):
        """Test that the backup listing endpoint exists."""
        response = await authenticated_client.get("/api/config/backups")
        
        assert response.status_code != 404

    async def test_list_backups_returns_array(self, authenticated_client):
        """Test that listing backups returns an array."""
        response = await authenticated_client.get("/api/config/backups")
        
        assert response.status_code == 200
        
        data = response.json()
        assert isinstance(data, list)

    async def test_list_backups_contains_metadata(self, authenticated_client):
        """Test that backup list contains metadata for each backup."""
        # Create a backup first
        await authenticated_client.post("/api/config/backups")
        
        # List backups
        response = await authenticated_client.get("/api/config/backups")
        assert response.status_code == 200
        
        backups = response.json()
        
        if len(backups) > 0:
            backup = backups[0]
            
            # Should have metadata fields
            assert "name" in backup
            # May also have: size, created, modified, etc.

    async def test_list_backups_shows_recently_created(self, authenticated_client):
        """Test that newly created backups appear in list."""
        # Create backup
        create_response = await authenticated_client.post("/api/config/backups")
        assert create_response.status_code in [200, 201]
        
        backup_name = create_response.json()["name"]
        
        # List backups
        list_response = await authenticated_client.get("/api/config/backups")
        assert list_response.status_code == 200
        
        backups = list_response.json()
        backup_names = [b["name"] for b in backups]
        
        # New backup should be in list
        assert backup_name in backup_names

    async def test_list_backups_requires_authentication(self, authenticated_client):
        """Test that listing backups requires authentication."""
        transport = ASGITransport(app=app)
        async with AsyncClient(transport=transport, base_url="http://test") as client:
            response = await client.get("/api/config/backups")
            
            assert response.status_code == 401


class TestBackupRestoration:
    """Tests for restoring configuration from backups."""

    async def test_restore_backup_endpoint_exists(self, authenticated_client):
        """Test that the backup restoration endpoint exists."""
        # Try with dummy backup name
        response = await authenticated_client.post(
            "/api/config/backups/dummy_backup.json/restore"
        )
        
        # Should not return 404 (may return 404 for missing backup, but endpoint exists)
        assert response.status_code != 500

    async def test_restore_backup_with_valid_backup(self, authenticated_client):
        """Test restoring from a valid backup."""
        # Create a backup first
        create_response = await authenticated_client.post("/api/config/backups")
        assert create_response.status_code in [200, 201]
        
        backup_name = create_response.json()["name"]
        
        # Restore from backup
        restore_response = await authenticated_client.post(
            f"/api/config/backups/{backup_name}/restore"
        )
        
        # Should succeed
        assert restore_response.status_code == 200
        
        # Should return restored configuration
        config = restore_response.json()
        assert isinstance(config, dict)

    async def test_restore_nonexistent_backup_fails(self, authenticated_client):
        """Test that restoring nonexistent backup returns error."""
        response = await authenticated_client.post(
            "/api/config/backups/nonexistent_backup_12345.json/restore"
        )
        
        # Should return 404 Not Found
        assert response.status_code == 404

    async def test_restore_creates_backup_before_restoring(self, authenticated_client):
        """Test that restore creates backup of current config first."""
        # Get initial backup count
        list_response1 = await authenticated_client.get("/api/config/backups")
        initial_count = len(list_response1.json())
        
        # Create a backup
        create_response = await authenticated_client.post("/api/config/backups")
        assert create_response.status_code in [200, 201]
        backup_name = create_response.json()["name"]
        
        # Restore (this should create another backup)
        await authenticated_client.post(
            f"/api/config/backups/{backup_name}/restore"
        )
        
        # Check backup count increased
        list_response2 = await authenticated_client.get("/api/config/backups")
        final_count = len(list_response2.json())
        
        # Should have at least 2 more backups (original + pre-restore)
        # but max_backups limit may prune old ones
        config_service = get_config_service()
        expected = min(initial_count + 2, config_service.max_backups)
        assert final_count >= expected

    async def test_restore_requires_authentication(self, authenticated_client):
        """Test that restore requires authentication."""
        transport = ASGITransport(app=app)
        async with AsyncClient(transport=transport, base_url="http://test") as client:
            response = await client.post(
                "/api/config/backups/any_backup.json/restore"
            )
            
            assert response.status_code == 401

    async def test_restored_config_matches_backup(self, authenticated_client):
        """Test that restored configuration matches backup content."""
        # Get current config
        config_response1 = await authenticated_client.get("/api/config")
        original_config = config_response1.json()
        
        # Create backup
        create_response = await authenticated_client.post("/api/config/backups")
        backup_name = create_response.json()["name"]
        
        # Make a change to config (if possible)
        # Then restore
        restore_response = await authenticated_client.post(
            f"/api/config/backups/{backup_name}/restore"
        )
        
        if restore_response.status_code == 200:
            restored_config = restore_response.json()
            
            # Key fields should match original
            if "name" in original_config:
                assert restored_config.get("name") == original_config.get("name")


class TestBackupDeletion:
    """Tests for deleting configuration backups."""

    async def test_delete_backup_endpoint_exists(self, authenticated_client):
        """Test that the backup deletion endpoint exists."""
        response = await authenticated_client.delete(
            "/api/config/backups/dummy_backup.json"
        )
        
        # Should not return 404 (endpoint exists, backup might not)
        assert response.status_code != 500

    async def test_delete_existing_backup(self, authenticated_client):
        """Test deleting an existing backup."""
        # Create a backup
        create_response = await authenticated_client.post("/api/config/backups")
        assert create_response.status_code in [200, 201]
        
        backup_name = create_response.json()["name"]
        
        # Delete the backup
        delete_response = await authenticated_client.delete(
            f"/api/config/backups/{backup_name}"
        )
        
        # Should succeed
        assert delete_response.status_code == 200
        
        data = delete_response.json()
        assert "message" in data or "success" in data

    async def test_delete_removes_backup_from_list(self, authenticated_client):
        """Test that deleted backup no longer appears in list."""
        # Create backup
        create_response = await authenticated_client.post("/api/config/backups")
        backup_name = create_response.json()["name"]
        
        # Verify it's in the list
        list_response1 = await authenticated_client.get("/api/config/backups")
        backup_names1 = [b["name"] for b in list_response1.json()]
        assert backup_name in backup_names1
        
        # Delete backup
        await authenticated_client.delete(f"/api/config/backups/{backup_name}")
        
        # Verify it's no longer in list
        list_response2 = await authenticated_client.get("/api/config/backups")
        backup_names2 = [b["name"] for b in list_response2.json()]
        assert backup_name not in backup_names2

    async def test_delete_removes_backup_file(self, authenticated_client):
        """Test that backup file is removed from disk."""
        # Create backup
        create_response = await authenticated_client.post("/api/config/backups")
        backup_name = create_response.json()["name"]
        
        # Verify file exists
        config_service = get_config_service()
        backup_dir = config_service.backup_dir
        backup_file = backup_dir / backup_name
        
        if backup_file.exists():
            initial_exists = True
        else:
            initial_exists = False
        
        # Delete backup
        await authenticated_client.delete(f"/api/config/backups/{backup_name}")
        
        # File should no longer exist
        if initial_exists:
            assert not backup_file.exists()

    async def test_delete_nonexistent_backup_fails(self, authenticated_client):
        """Test that deleting nonexistent backup returns error."""
        response = await authenticated_client.delete(
            "/api/config/backups/nonexistent_backup_99999.json"
        )
        
        # Should return 404 Not Found
        assert response.status_code == 404

    async def test_delete_requires_authentication(self, authenticated_client):
        """Test that deletion requires authentication."""
        transport = ASGITransport(app=app)
        async with AsyncClient(transport=transport, base_url="http://test") as client:
            response = await client.delete(
                "/api/config/backups/any_backup.json"
            )
            
            assert response.status_code == 401


class TestBackupWorkflow:
    """Tests for complete backup/restore workflows."""

    async def test_full_backup_restore_workflow(self, authenticated_client):
        """Test complete backup and restore workflow."""
        # Step 1: Get current config
        config1 = await authenticated_client.get("/api/config")
        original_config = config1.json()
        
        # Step 2: Create backup
        backup_response = await authenticated_client.post("/api/config/backups")
        assert backup_response.status_code in [200, 201]
        backup_name = backup_response.json()["name"]
        
        # Step 3: List backups and verify presence
        list_response = await authenticated_client.get("/api/config/backups")
        assert list_response.status_code == 200
        backup_names = [b["name"] for b in list_response.json()]
        assert backup_name in backup_names
        
        # Step 4: Restore backup
        restore_response = await authenticated_client.post(
            f"/api/config/backups/{backup_name}/restore"
        )
        assert restore_response.status_code == 200
        
        # Step 5: Verify config matches
        config2 = await authenticated_client.get("/api/config")
        restored_config = config2.json()
        
        # Key fields should match
        if "name" in original_config:
            assert restored_config.get("name") == original_config.get("name")

    async def test_multiple_backup_restore_cycles(self, authenticated_client):
        """Test multiple backup and restore cycles."""
        backup_names = []
        
        # Create multiple backups
        for i in range(3):
            response = await authenticated_client.post("/api/config/backups")
            if response.status_code in [200, 201]:
                backup_names.append(response.json()["name"])
            
            import asyncio
            await asyncio.sleep(0.1)
        
        # Restore from each backup
        for backup_name in backup_names:
            response = await authenticated_client.post(
                f"/api/config/backups/{backup_name}/restore"
            )
            assert response.status_code == 200

    async def test_backup_after_config_change(self, authenticated_client):
        """Test creating backup after configuration change."""
        # Make a config change (if possible)
        update_data = {
            "name": "Modified Config"
        }
        
        update_response = await authenticated_client.put(
            "/api/config",
            json=update_data
        )
        
        # Create backup with changed config
        backup_response = await authenticated_client.post("/api/config/backups")
        
        if backup_response.status_code in [200, 201]:
            backup_name = backup_response.json()["name"]
            
            # Backup should contain the change
            config_service = get_config_service()
            backup_dir = config_service.backup_dir
            backup_file = backup_dir / backup_name
            
            if backup_file.exists():
                with open(backup_file, 'r') as f:
                    backup_data = json.load(f)
                
                # Should contain updated config
                assert isinstance(backup_data, dict)


class TestBackupEdgeCases:
    """Tests for edge cases in backup/restore operations."""

    async def test_restore_with_invalid_backup_name(self, authenticated_client):
        """Test restore with invalid backup name format."""
        invalid_names = [
            "../../../etc/passwd",
            "backup; rm -rf /",
        ]
        
        for invalid_name in invalid_names:
            response = await authenticated_client.post(
                f"/api/config/backups/{invalid_name}/restore"
            )
            
            # Should reject invalid names or handle them gracefully
            assert response.status_code in [200, 400, 404, 422, 500]

    async def test_concurrent_backup_operations(self, authenticated_client):
        """Test multiple concurrent backup operations."""
        import asyncio

        # Create multiple backups concurrently
        tasks = [
            authenticated_client.post("/api/config/backups")
            for _ in range(5)
        ]
        
        responses = await asyncio.gather(*tasks, return_exceptions=True)
        
        # All should succeed or handle gracefully
        for response in responses:
            if not isinstance(response, Exception):
                assert response.status_code in [200, 201, 429]  # 429 = too many requests

    async def test_backup_with_very_long_custom_name(self, authenticated_client):
        """Test backup creation with very long custom name."""
        long_name = "a" * 500
        
        response = await authenticated_client.post(
            "/api/config/backups",
            params={"name": long_name}
        )
        
        # Should handle gracefully (accept or reject with proper error)
        assert response.status_code in [200, 201, 400]

    async def test_backup_preserves_all_configuration_sections(self, authenticated_client):
        """Test that backup preserves all configuration sections."""
        # Create backup
        create_response = await authenticated_client.post("/api/config/backups")
        assert create_response.status_code in [200, 201]
        
        backup_name = create_response.json()["name"]
        
        # Read backup file
        config_service = get_config_service()
        backup_dir = config_service.backup_dir
        backup_file = backup_dir / backup_name
        
        if backup_file.exists():
            with open(backup_file, 'r') as f:
                backup_data = json.load(f)
            
            # Should have major configuration sections
            # (exact structure depends on AppConfig model)
            assert isinstance(backup_data, dict)
            # Could check for specific keys like 'scheduler', 'logging', 'nfo', etc.
