# (c) cavaliba.com - tests / log2 - event count, list, purge and retention

from datetime import timedelta

from django.test import TestCase
from django.utils import timezone

import app_home.cache as cache
from app_home.log import (
    event_count,
    event_list,
    event_purge,
    event_purge_all,
    event_purge_level,
    log2,
)
from app_home.models import CavalibaConfiguration, CavalibaEvent


class TestLog2Purge(TestCase):
    def setUp(self):
        cache.clear()
        CavalibaEvent.objects.all().delete()
        log2.reset(channel="cli")

    def tearDown(self):
        # configuration rows roll back with the test, the in-memory cache does not
        cache.clear()

    def _age(self, action, days):
        CavalibaEvent.objects.filter(action=action).update(
            created=timezone.now() - timedelta(days=days)
        )

    def _config(self, keyname, value):
        CavalibaConfiguration.objects.update_or_create(
            appname="home", keyname=keyname, defaults={"value": value}
        )
        cache.clear()

    def test_count_and_list_offset(self):
        for i in range(5):
            log2.ok(f"test.e{i}")
        self.assertEqual(event_count(), 5)
        self.assertEqual([e.action for e in event_list(0, 2)], ["test.e4", "test.e3"])
        self.assertEqual([e.action for e in event_list(2, 2)], ["test.e2", "test.e1"])

    def test_purge_level_by_age(self):
        log2.ok("test.old")
        log2.ok("test.new")
        self._age("test.old", 10)
        self.assertEqual(event_purge_level("INFO", 5), 1)
        self.assertEqual(list(CavalibaEvent.objects.values_list("action", flat=True)), ["test.new"])

    def test_purge_level_zero_days_removes_all_of_that_level(self):
        log2.ok("test.info")
        log2.fail("test.error")
        self.assertEqual(event_purge_level("INFO", 0), 1)
        self.assertTrue(CavalibaEvent.objects.filter(action="test.error").exists())

    def test_purge_applies_keep_days_per_level(self):
        self._config("LOG_KEEP_DAYS_DEBUG", "7")
        self._config("LOG_KEEP_DAYS_ERROR", "365")
        log2.access("test.debug")
        log2.fail("test.error")
        self._age("test.debug", 30)
        self._age("test.error", 30)
        event_purge()
        actions = set(CavalibaEvent.objects.values_list("action", flat=True))
        self.assertNotIn("test.debug", actions)
        self.assertIn("test.error", actions)
        self.assertIn("log.purge", actions)

    def test_purge_applies_max_entries(self):
        self._config("LOG_MAX_ENTRIES", "3")
        for i in range(10):
            log2.fail(f"test.e{i}")
        event_purge()
        self.assertLessEqual(CavalibaEvent.objects.exclude(action="log.purge").count(), 3)
        self.assertTrue(CavalibaEvent.objects.filter(action="test.e9").exists())

    def test_purge_all(self):
        log2.ok("test.a")
        log2.ok("test.b")
        self.assertEqual(event_purge_all(), 2)
        self.assertEqual(
            list(CavalibaEvent.objects.values_list("action", flat=True)), ["log.purge_all"]
        )

    def test_max_entries_evicts_debug_first(self):
        self._config("LOG_MAX_ENTRIES", "4")
        for i in range(3):
            log2.fail(f"test.err{i}")
        for i in range(5):
            log2.access(f"test.dbg{i}")
        event_purge()
        remaining = set(
            CavalibaEvent.objects.exclude(action="log.purge").values_list("action", flat=True)
        )
        self.assertTrue({"test.err0", "test.err1", "test.err2"} <= remaining)
        self.assertEqual(len(remaining), 4)
        self.assertIn("test.dbg4", remaining)
