"""Tests for RetentionService: cleanup and purge logic.

Uses mocked async_session_maker since RetentionService creates its own sessions.
"""
import uuid
from datetime import datetime, timedelta, timezone
from unittest.mock import AsyncMock, MagicMock, patch

import pytest

from models.sql.client import ClientModel
from models.sql.presentation import PresentationModel
from services.retention_service import RetentionService


@pytest.fixture
def svc():
    return RetentionService()


class TestRunCleanup:
    async def test_soft_deletes_expired_presentations(
        self, svc, session, make_client, make_presentation, make_user
    ):
        """Presentations older than client.retention_days get soft-deleted."""
        client = await make_client(name="RetCorp", slug="retcorp", retention_days=30)
        user = await make_user(email="ret@test.com")

        # Old presentation (60 days ago) — should be deleted
        old = await make_presentation(
            owner_id=user.id,
            client_id=client.id,
            title="Old Deck",
            created_at=datetime.now(timezone.utc) - timedelta(days=60),
        )
        # Recent presentation — should survive
        recent = await make_presentation(
            owner_id=user.id,
            client_id=client.id,
            title="New Deck",
            created_at=datetime.now(timezone.utc) - timedelta(days=5),
        )

        # Mock async_session_maker to use our test session
        with patch("services.retention_service.async_session_maker") as mock_sm:
            mock_sm.return_value.__aenter__ = AsyncMock(return_value=session)
            mock_sm.return_value.__aexit__ = AsyncMock(return_value=False)

            result = await svc.run_cleanup()

        assert result["deleted"] == 1

        await session.refresh(old)
        await session.refresh(recent)
        assert old.deleted_at is not None
        assert recent.deleted_at is None

    async def test_no_clients_with_retention_policy(self, session, make_client):
        """Clients without retention_days are skipped."""
        await make_client(name="NoPol", slug="nopol", retention_days=None)

        with patch("services.retention_service.async_session_maker") as mock_sm:
            mock_sm.return_value.__aenter__ = AsyncMock(return_value=session)
            mock_sm.return_value.__aexit__ = AsyncMock(return_value=False)

            result = await RetentionService().run_cleanup()

        assert result["deleted"] == 0


class TestPurgeSoftDeleted:
    async def test_purges_old_soft_deleted(
        self, session, make_client, make_presentation, make_user
    ):
        """Soft-deleted presentations older than threshold get permanently deleted."""
        client = await make_client(name="Purge Corp", slug="purge-corp")
        user = await make_user(email="purge@test.com")

        # Soft-deleted 45 days ago — should be purged
        pres = await make_presentation(
            owner_id=user.id,
            client_id=client.id,
            title="Dead Deck",
            deleted_at=datetime.now(timezone.utc) - timedelta(days=45),
        )

        with patch("services.retention_service.async_session_maker") as mock_sm:
            mock_sm.return_value.__aenter__ = AsyncMock(return_value=session)
            mock_sm.return_value.__aexit__ = AsyncMock(return_value=False)

            result = await RetentionService().purge_soft_deleted(days_after_soft_delete=30)

        assert result["purged"] == 1

        # Should be gone from DB
        deleted = await session.get(PresentationModel, pres.id)
        assert deleted is None


class TestCleanupFiles:
    def test_cleanup_files_removes_existing_files(self, svc, tmp_path):
        """_cleanup_files removes files listed in presentation.file_paths."""
        f = tmp_path / "test.pptx"
        f.write_text("dummy")

        pres = MagicMock()
        pres.id = uuid.uuid4()
        pres.file_paths = [str(f)]

        svc._cleanup_files(pres)
        assert not f.exists()

    def test_cleanup_files_handles_missing_files(self, svc):
        """Missing files don't raise errors."""
        pres = MagicMock()
        pres.id = uuid.uuid4()
        pres.file_paths = ["/nonexistent/file.pptx"]

        # Should not raise
        svc._cleanup_files(pres)

    def test_cleanup_files_handles_none(self, svc):
        pres = MagicMock()
        pres.id = uuid.uuid4()
        pres.file_paths = None

        svc._cleanup_files(pres)
