"""TTL-purge of blocked-bundle bytes.

Covers:
* Bundles older than TTL get rmtree'd, entity row deleted, submission
  row stamped with bundle_purged_at and entity_id nulled. Sha + size
  survive on the row for forensic correlation.
* Approved / overridden / pending submissions are not touched.
* Idempotent — running twice doesn't re-purge.
* ttl_days=0 short-circuits to a no-op.
"""

from __future__ import annotations

import shutil
import tempfile
from datetime import datetime, timedelta, timezone
from pathlib import Path

import pytest


@pytest.fixture
def conn(tmp_path, monkeypatch):
    monkeypatch.setenv("DATA_DIR", str(tmp_path))
    from src import db
    db._system_db_conn = None
    db._system_db_path = None
    c = db.get_system_db()
    yield c
    c.close()


def _seed_with_bundle(conn, store_root: Path, owner_id: str, name: str,
                      status: str, days_old: int) -> tuple[str, str]:
    """Stage entity + bundle on disk + submission row at a given age."""
    import uuid
    from src.repositories.store_entities import StoreEntitiesRepository
    from src.repositories.store_submissions import StoreSubmissionsRepository
    from src.repositories.users import UserRepository

    UserRepository(conn).create(id=owner_id, email=f"{owner_id}@x.com", name=owner_id)

    eid = uuid.uuid4().hex
    plugin_dir = store_root / eid / "plugin" / "skills" / name
    plugin_dir.mkdir(parents=True, exist_ok=True)
    (plugin_dir / "SKILL.md").write_text(f"---\nname: {name}\n---\nbody", encoding="utf-8")

    StoreEntitiesRepository(conn).create(
        id=eid, owner_user_id=owner_id, owner_username=owner_id,
        type="skill", name=name, description="x", category=None,
        version="1.0.0", file_size=10, visibility_status="hidden",
    )

    sub_id = StoreSubmissionsRepository(conn).create(
        submitter_id=owner_id, submitter_email=f"{owner_id}@x.com",
        type="skill", name=name, version="1.0.0",
        status=status, entity_id=eid,
        file_size=42, bundle_sha256="deadbeef" * 8,
    )
    # Backdate created_at so the TTL test sees the row as old.
    if days_old > 0:
        old = datetime.now(timezone.utc) - timedelta(days=days_old)
        conn.execute(
            "UPDATE store_submissions SET created_at = ? WHERE id = ?",
            [old, sub_id],
        )
    return sub_id, eid


class TestPurgeBlockedBundles:
    def test_purges_old_blocked_bundle(self, conn, tmp_path):
        from src.store_guardrails.purge import purge_blocked_bundles
        from src.repositories.store_submissions import StoreSubmissionsRepository
        from src.repositories.store_entities import StoreEntitiesRepository

        sub_id, eid = _seed_with_bundle(
            conn, tmp_path / "store", "u1", "old-bad",
            status="blocked_inline", days_old=45,
        )
        plugin_dir = tmp_path / "store" / eid / "plugin"
        assert plugin_dir.exists()

        result = purge_blocked_bundles(
            conn, ttl_days=30,
            store_dir_resolver=lambda: tmp_path / "store",
        )
        assert result["purged"] == 1
        assert sub_id in result["ids"]

        sub = StoreSubmissionsRepository(conn).get(sub_id)
        assert sub["bundle_purged_at"] is not None
        assert sub["entity_id"] is None
        # SHA + size survive for forensics
        assert sub["bundle_sha256"] == "deadbeef" * 8
        assert sub["file_size"] == 42

        assert not plugin_dir.exists()
        assert StoreEntitiesRepository(conn).get(eid) is None

    def test_skips_recent_blocked(self, conn, tmp_path):
        from src.store_guardrails.purge import purge_blocked_bundles
        from src.repositories.store_submissions import StoreSubmissionsRepository

        sub_id, eid = _seed_with_bundle(
            conn, tmp_path / "store", "u1", "fresh-bad",
            status="blocked_inline", days_old=2,
        )
        result = purge_blocked_bundles(
            conn, ttl_days=30,
            store_dir_resolver=lambda: tmp_path / "store",
        )
        assert result["purged"] == 0
        sub = StoreSubmissionsRepository(conn).get(sub_id)
        assert sub["bundle_purged_at"] is None
        assert (tmp_path / "store" / eid / "plugin").exists()

    def test_skips_approved(self, conn, tmp_path):
        from src.store_guardrails.purge import purge_blocked_bundles

        sub_id, eid = _seed_with_bundle(
            conn, tmp_path / "store", "u1", "old-approved",
            status="approved", days_old=100,
        )
        result = purge_blocked_bundles(
            conn, ttl_days=30,
            store_dir_resolver=lambda: tmp_path / "store",
        )
        assert result["purged"] == 0
        assert (tmp_path / "store" / eid / "plugin").exists()

    def test_skips_overridden(self, conn, tmp_path):
        from src.store_guardrails.purge import purge_blocked_bundles

        sub_id, eid = _seed_with_bundle(
            conn, tmp_path / "store", "u1", "old-override",
            status="overridden", days_old=100,
        )
        result = purge_blocked_bundles(
            conn, ttl_days=30,
            store_dir_resolver=lambda: tmp_path / "store",
        )
        assert result["purged"] == 0

    def test_idempotent(self, conn, tmp_path):
        from src.store_guardrails.purge import purge_blocked_bundles

        _seed_with_bundle(
            conn, tmp_path / "store", "u1", "x",
            status="blocked_llm", days_old=45,
        )
        first = purge_blocked_bundles(
            conn, ttl_days=30,
            store_dir_resolver=lambda: tmp_path / "store",
        )
        assert first["purged"] == 1
        # Second run must purge nothing (bundle_purged_at already set,
        # entity_id is null).
        second = purge_blocked_bundles(
            conn, ttl_days=30,
            store_dir_resolver=lambda: tmp_path / "store",
        )
        assert second["purged"] == 0

    def test_ttl_zero_is_noop(self, conn, tmp_path):
        from src.store_guardrails.purge import purge_blocked_bundles

        sub_id, eid = _seed_with_bundle(
            conn, tmp_path / "store", "u1", "x",
            status="blocked_inline", days_old=999,
        )
        result = purge_blocked_bundles(
            conn, ttl_days=0,
            store_dir_resolver=lambda: tmp_path / "store",
        )
        assert result == {"purged": 0, "ids": [], "skipped": True}
        assert (tmp_path / "store" / eid / "plugin").exists()
