"""Tests for stale push operation recovery on API startup."""

import pytest
from datetime import datetime, timedelta, timezone
from unittest.mock import AsyncMock, patch, MagicMock
from uuid import uuid4

from app.services.restore_service import recover_stale_push_operations


@pytest.mark.asyncio
async def test_recovery_commits_reachable_device_with_scheduler():
    """If device is reachable and panic-revert scheduler exists, delete it and commit."""
    push_op = MagicMock()
    push_op.id = uuid4()
    push_op.device_id = uuid4()
    push_op.tenant_id = uuid4()
    push_op.status = "pending_verification"
    push_op.scheduler_name = "the-other-dude-panic-revert"
    push_op.started_at = datetime.now(timezone.utc) - timedelta(minutes=10)

    device = MagicMock()
    device.ip_address = "192.168.1.1"
    device.api_port = 8729
    device.ssh_port = 22

    mock_session = AsyncMock()
    # Return stale ops query
    mock_result = MagicMock()
    mock_result.scalars.return_value.all.return_value = [push_op]
    mock_session.execute = AsyncMock(side_effect=[mock_result, MagicMock()])

    # Mock device query result (second execute call)
    dev_result = MagicMock()
    dev_result.scalar_one_or_none.return_value = device
    mock_session.execute = AsyncMock(side_effect=[mock_result, dev_result])

    with (
        patch(
            "app.services.restore_service._check_reachability",
            new_callable=AsyncMock,
            return_value=True,
        ),
        patch(
            "app.services.restore_service._remove_panic_scheduler",
            new_callable=AsyncMock,
            return_value=True,
        ),
        patch(
            "app.services.restore_service._update_push_op_status",
            new_callable=AsyncMock,
        ) as mock_update,
        patch(
            "app.services.restore_service._publish_push_progress",
            new_callable=AsyncMock,
        ),
        patch(
            "app.services.crypto.decrypt_credentials_hybrid",
            new_callable=AsyncMock,
            return_value='{"username": "admin", "password": "test123"}',
        ),
        patch(
            "app.services.restore_service.settings",
        ),
    ):
        await recover_stale_push_operations(mock_session)

    mock_update.assert_called_once()
    call_args = mock_update.call_args
    assert call_args[0][1] == "committed" or call_args[1].get("new_status") == "committed"


@pytest.mark.asyncio
async def test_recovery_marks_unreachable_device_failed():
    """If device is unreachable, mark operation as failed."""
    push_op = MagicMock()
    push_op.id = uuid4()
    push_op.device_id = uuid4()
    push_op.tenant_id = uuid4()
    push_op.status = "pending_verification"
    push_op.scheduler_name = "the-other-dude-panic-revert"
    push_op.started_at = datetime.now(timezone.utc) - timedelta(minutes=10)

    device = MagicMock()
    device.ip_address = "192.168.1.1"

    mock_session = AsyncMock()
    mock_result = MagicMock()
    mock_result.scalars.return_value.all.return_value = [push_op]
    dev_result = MagicMock()
    dev_result.scalar_one_or_none.return_value = device
    mock_session.execute = AsyncMock(side_effect=[mock_result, dev_result])

    with (
        patch(
            "app.services.restore_service._check_reachability",
            new_callable=AsyncMock,
            return_value=False,
        ),
        patch(
            "app.services.restore_service._update_push_op_status",
            new_callable=AsyncMock,
        ) as mock_update,
        patch(
            "app.services.restore_service._publish_push_progress",
            new_callable=AsyncMock,
        ),
        patch(
            "app.services.crypto.decrypt_credentials_hybrid",
            new_callable=AsyncMock,
            return_value='{"username": "admin", "password": "test123"}',
        ),
        patch(
            "app.services.restore_service.settings",
        ),
    ):
        await recover_stale_push_operations(mock_session)

    mock_update.assert_called_once()
    call_args = mock_update.call_args
    assert call_args[0][1] == "failed" or call_args[1].get("new_status") == "failed"


@pytest.mark.asyncio
async def test_recovery_skips_recent_ops():
    """Operations less than 5 minutes old should not be recovered (still in progress)."""
    mock_session = AsyncMock()
    mock_result = MagicMock()
    mock_result.scalars.return_value.all.return_value = []  # Query filters by age
    mock_session.execute = AsyncMock(return_value=mock_result)

    await recover_stale_push_operations(mock_session)
    # No errors, no updates — just returns cleanly
