"""
Tests for the workspace management module.
"""

from datetime import UTC, datetime, timedelta
from pathlib import Path

import pytest

from exceptions import WorkspaceLockedError, WorkspaceNotFoundError
from models import WorkspaceState
from workspace import FileLockManager, WorkspaceLock


class TestWorkspaceManager:
    """Tests for WorkspaceManager."""

    @pytest.mark.asyncio
    async def test_create_workspace(self, workspace_manager, valid_project_id):
        """Test creating a new workspace."""
        workspace = await workspace_manager.create_workspace(valid_project_id)

        assert workspace.project_id == valid_project_id
        assert workspace.state == WorkspaceState.INITIALIZING
        assert Path(workspace.path).exists()

    @pytest.mark.asyncio
    async def test_create_workspace_with_repo_url(
        self, workspace_manager, valid_project_id, sample_repo_url
    ):
        """Test creating workspace with repository URL."""
        workspace = await workspace_manager.create_workspace(
            valid_project_id, repo_url=sample_repo_url
        )

        assert workspace.repo_url == sample_repo_url

    @pytest.mark.asyncio
    async def test_get_workspace(self, workspace_manager, valid_project_id):
        """Test getting an existing workspace."""
        # Create first
        await workspace_manager.create_workspace(valid_project_id)

        # Get it
        workspace = await workspace_manager.get_workspace(valid_project_id)

        assert workspace is not None
        assert workspace.project_id == valid_project_id

    @pytest.mark.asyncio
    async def test_get_workspace_not_found(self, workspace_manager):
        """Test getting non-existent workspace."""
        workspace = await workspace_manager.get_workspace("nonexistent")
        assert workspace is None

    @pytest.mark.asyncio
    async def test_delete_workspace(self, workspace_manager, valid_project_id):
        """Test deleting a workspace."""
        # Create first
        workspace = await workspace_manager.create_workspace(valid_project_id)
        workspace_path = Path(workspace.path)
        assert workspace_path.exists()

        # Delete
        result = await workspace_manager.delete_workspace(valid_project_id)

        assert result is True
        assert not workspace_path.exists()

    @pytest.mark.asyncio
    async def test_delete_nonexistent_workspace(self, workspace_manager):
        """Test deleting non-existent workspace returns True."""
        result = await workspace_manager.delete_workspace("nonexistent")
        assert result is True

    @pytest.mark.asyncio
    async def test_list_workspaces(self, workspace_manager):
        """Test listing workspaces."""
        # Create multiple workspaces
        await workspace_manager.create_workspace("project-1")
        await workspace_manager.create_workspace("project-2")
        await workspace_manager.create_workspace("project-3")

        workspaces = await workspace_manager.list_workspaces()

        assert len(workspaces) >= 3
        project_ids = [w.project_id for w in workspaces]
        assert "project-1" in project_ids
        assert "project-2" in project_ids
        assert "project-3" in project_ids


class TestWorkspaceLocking:
    """Tests for workspace locking."""

    @pytest.mark.asyncio
    async def test_lock_workspace(
        self, workspace_manager, valid_project_id, valid_agent_id
    ):
        """Test locking a workspace."""
        await workspace_manager.create_workspace(valid_project_id)

        result = await workspace_manager.lock_workspace(
            valid_project_id, valid_agent_id, timeout=60
        )

        assert result is True

        workspace = await workspace_manager.get_workspace(valid_project_id)
        assert workspace.state == WorkspaceState.LOCKED
        assert workspace.lock_holder == valid_agent_id

    @pytest.mark.asyncio
    async def test_lock_already_locked(self, workspace_manager, valid_project_id):
        """Test locking already-locked workspace by different holder."""
        await workspace_manager.create_workspace(valid_project_id)
        await workspace_manager.lock_workspace(valid_project_id, "agent-1", timeout=60)

        with pytest.raises(WorkspaceLockedError):
            await workspace_manager.lock_workspace(
                valid_project_id, "agent-2", timeout=60
            )

    @pytest.mark.asyncio
    async def test_lock_same_holder(
        self, workspace_manager, valid_project_id, valid_agent_id
    ):
        """Test re-locking by same holder extends lock."""
        await workspace_manager.create_workspace(valid_project_id)
        await workspace_manager.lock_workspace(
            valid_project_id, valid_agent_id, timeout=60
        )

        # Same holder can re-lock
        result = await workspace_manager.lock_workspace(
            valid_project_id, valid_agent_id, timeout=120
        )

        assert result is True

    @pytest.mark.asyncio
    async def test_unlock_workspace(
        self, workspace_manager, valid_project_id, valid_agent_id
    ):
        """Test unlocking a workspace."""
        await workspace_manager.create_workspace(valid_project_id)
        await workspace_manager.lock_workspace(valid_project_id, valid_agent_id)

        result = await workspace_manager.unlock_workspace(
            valid_project_id, valid_agent_id
        )

        assert result is True
        workspace = await workspace_manager.get_workspace(valid_project_id)
        assert workspace.state == WorkspaceState.READY
        assert workspace.lock_holder is None

    @pytest.mark.asyncio
    async def test_unlock_wrong_holder(self, workspace_manager, valid_project_id):
        """Test unlock fails with wrong holder."""
        await workspace_manager.create_workspace(valid_project_id)
        await workspace_manager.lock_workspace(valid_project_id, "agent-1")

        with pytest.raises(WorkspaceLockedError):
            await workspace_manager.unlock_workspace(valid_project_id, "agent-2")

    @pytest.mark.asyncio
    async def test_force_unlock(self, workspace_manager, valid_project_id):
        """Test force unlock works regardless of holder."""
        await workspace_manager.create_workspace(valid_project_id)
        await workspace_manager.lock_workspace(valid_project_id, "agent-1")

        result = await workspace_manager.unlock_workspace(
            valid_project_id, "admin", force=True
        )

        assert result is True

    @pytest.mark.asyncio
    async def test_lock_nonexistent_workspace(self, workspace_manager, valid_agent_id):
        """Test locking non-existent workspace raises error."""
        with pytest.raises(WorkspaceNotFoundError):
            await workspace_manager.lock_workspace("nonexistent", valid_agent_id)


class TestWorkspaceLockContextManager:
    """Tests for WorkspaceLock context manager."""

    @pytest.mark.asyncio
    async def test_lock_context_manager(
        self, workspace_manager, valid_project_id, valid_agent_id
    ):
        """Test using WorkspaceLock as context manager."""
        await workspace_manager.create_workspace(valid_project_id)

        async with WorkspaceLock(
            workspace_manager, valid_project_id, valid_agent_id
        ) as lock:
            workspace = await workspace_manager.get_workspace(valid_project_id)
            assert workspace.state == WorkspaceState.LOCKED

        # After exiting context, should be unlocked
        workspace = await workspace_manager.get_workspace(valid_project_id)
        assert workspace.lock_holder is None

    @pytest.mark.asyncio
    async def test_lock_context_manager_error(
        self, workspace_manager, valid_project_id, valid_agent_id
    ):
        """Test WorkspaceLock releases on exception."""
        await workspace_manager.create_workspace(valid_project_id)

        try:
            async with WorkspaceLock(
                workspace_manager, valid_project_id, valid_agent_id
            ):
                raise ValueError("Test error")
        except ValueError:
            pass

        workspace = await workspace_manager.get_workspace(valid_project_id)
        assert workspace.lock_holder is None


class TestWorkspaceMetadata:
    """Tests for workspace metadata operations."""

    @pytest.mark.asyncio
    async def test_touch_workspace(self, workspace_manager, valid_project_id):
        """Test updating workspace access time."""
        workspace = await workspace_manager.create_workspace(valid_project_id)
        original_time = workspace.last_accessed

        await workspace_manager.touch_workspace(valid_project_id)

        updated = await workspace_manager.get_workspace(valid_project_id)
        assert updated.last_accessed >= original_time

    @pytest.mark.asyncio
    async def test_update_workspace_branch(self, workspace_manager, valid_project_id):
        """Test updating workspace branch."""
        await workspace_manager.create_workspace(valid_project_id)

        await workspace_manager.update_workspace_branch(
            valid_project_id, "feature-branch"
        )

        workspace = await workspace_manager.get_workspace(valid_project_id)
        assert workspace.current_branch == "feature-branch"


class TestWorkspaceSize:
    """Tests for workspace size management."""

    @pytest.mark.asyncio
    async def test_check_size_within_limit(self, workspace_manager, valid_project_id):
        """Test size check passes for small workspace."""
        await workspace_manager.create_workspace(valid_project_id)

        # Should not raise
        result = await workspace_manager.check_size_limit(valid_project_id)
        assert result is True

    @pytest.mark.asyncio
    async def test_get_total_size(self, workspace_manager, valid_project_id):
        """Test getting total workspace size."""
        workspace = await workspace_manager.create_workspace(valid_project_id)

        # Add some content
        content_file = Path(workspace.path) / "content.txt"
        content_file.write_text("x" * 1000)

        total_size = await workspace_manager.get_total_size()
        assert total_size >= 1000


class TestFileLockManager:
    """Tests for file-based locking."""

    def test_acquire_lock(self, temp_dir):
        """Test acquiring a file lock."""
        manager = FileLockManager(temp_dir / "locks")

        result = manager.acquire("test-key")
        assert result is True

        # Cleanup
        manager.release("test-key")

    def test_release_lock(self, temp_dir):
        """Test releasing a file lock."""
        manager = FileLockManager(temp_dir / "locks")
        manager.acquire("test-key")

        result = manager.release("test-key")
        assert result is True

    def test_is_locked(self, temp_dir):
        """Test checking if locked."""
        manager = FileLockManager(temp_dir / "locks")

        assert manager.is_locked("test-key") is False

        manager.acquire("test-key")
        assert manager.is_locked("test-key") is True

        manager.release("test-key")

    def test_release_nonexistent_lock(self, temp_dir):
        """Test releasing a lock that doesn't exist."""
        manager = FileLockManager(temp_dir / "locks")

        # Should not raise
        result = manager.release("nonexistent")
        assert result is False


class TestWorkspaceCleanup:
    """Tests for workspace cleanup operations."""

    @pytest.mark.asyncio
    async def test_cleanup_stale_workspaces(self, workspace_manager, test_settings):
        """Test cleaning up stale workspaces."""
        # Create workspace
        workspace = await workspace_manager.create_workspace("stale-project")

        # Manually set it as stale by updating metadata
        await workspace_manager._update_metadata(
            "stale-project",
            last_accessed=(datetime.now(UTC) - timedelta(days=30)).isoformat(),
        )

        # Run cleanup
        cleaned = await workspace_manager.cleanup_stale_workspaces()

        assert cleaned >= 1

    @pytest.mark.asyncio
    async def test_delete_locked_workspace_blocked(
        self, workspace_manager, valid_project_id, valid_agent_id
    ):
        """Test deleting locked workspace is blocked without force."""
        await workspace_manager.create_workspace(valid_project_id)
        await workspace_manager.lock_workspace(valid_project_id, valid_agent_id)

        with pytest.raises(WorkspaceLockedError):
            await workspace_manager.delete_workspace(valid_project_id)

    @pytest.mark.asyncio
    async def test_delete_locked_workspace_force(
        self, workspace_manager, valid_project_id, valid_agent_id
    ):
        """Test force deleting locked workspace."""
        await workspace_manager.create_workspace(valid_project_id)
        await workspace_manager.lock_workspace(valid_project_id, valid_agent_id)

        result = await workspace_manager.delete_workspace(valid_project_id, force=True)
        assert result is True
