# (c) cavaliba.com - tests / telemetry - telemetry() record path and validation

from unittest import mock

from django.test import TestCase

import app_data.telemetry as T
import app_home.cache as cache
from app_data.models import DataTelemetryRaw
from tests.helper import set_telemetry_config


class TelemetryRecordTest(TestCase):
    def setUp(self):
        cache.clear()
        T.reset_cache()

    def test_counter_default_value_and_id(self):
        self.assertTrue(T.telemetry("auth.login", id="oidc"))
        raw = DataTelemetryRaw.objects.get()
        self.assertEqual(
            (raw.key, raw.type, raw.value, raw.distinct_id), ("auth.login", "counter", 1.0, "oidc")
        )

    def test_undeclared_key_defaults_to_counter(self):
        self.assertTrue(T.telemetry("x.hits"))
        self.assertEqual(DataTelemetryRaw.objects.get().type, "counter")

    def test_undeclared_key_uses_call_type(self):
        self.assertTrue(T.telemetry("x.latency", type="timer", value="12.5"))
        raw = DataTelemetryRaw.objects.get()
        self.assertEqual((raw.type, raw.value), ("timer", 12.5))

    def test_declared_type_mismatch_dropped(self):
        self.assertFalse(T.telemetry("auth.login", type="timer"))
        self.assertEqual(DataTelemetryRaw.objects.count(), 0)

    def test_invalid_type_dropped(self):
        self.assertFalse(T.telemetry("x.hits", type="meter"))

    def test_invalid_key_dropped(self):
        for key in ["Bad Key", "", "a..b", ".a", None, "x" * 129]:
            self.assertFalse(T.telemetry(key), key)
        self.assertEqual(DataTelemetryRaw.objects.count(), 0)

    def test_invalid_id_dropped(self):
        self.assertFalse(T.telemetry("x.hits", id="a b"))
        self.assertFalse(T.telemetry("x.hits", id="x" * 65))

    def test_invalid_value_dropped(self):
        for value in ["nan", "inf", "abc", True, [1]]:
            self.assertFalse(T.telemetry("x.gauge", type="gauge", value=value), value)
        self.assertEqual(DataTelemetryRaw.objects.count(), 0)

    def test_set_value_stored_as_svalue(self):
        self.assertTrue(T.telemetry("x.status", type="set", value="ok"))
        self.assertFalse(T.telemetry("x.status", type="set"))
        raw = DataTelemetryRaw.objects.get()
        self.assertEqual((raw.svalue, raw.value), ("ok", 1.0))

    def test_sample_scales_counter_value(self):
        with mock.patch("app_data.telemetry.random.random", return_value=0.1):
            self.assertTrue(T.telemetry("x.hits", option={"sample": 0.5}))
        self.assertEqual(DataTelemetryRaw.objects.get().value, 2.0)

    def test_sampled_out_event_not_stored(self):
        with mock.patch("app_data.telemetry.random.random", return_value=0.9):
            self.assertTrue(T.telemetry("x.hits", option={"sample": 0.5}))
        self.assertEqual(DataTelemetryRaw.objects.count(), 0)

    def test_invalid_sample_dropped(self):
        self.assertFalse(T.telemetry("x.hits", option={"sample": 0}))
        self.assertFalse(T.telemetry("x.hits", option={"sample": 2}))

    def test_disabled_skips_silently(self):
        set_telemetry_config("TELEMETRY_ENABLED", "no")
        self.assertTrue(T.telemetry("auth.login"))
        self.assertEqual(DataTelemetryRaw.objects.count(), 0)

    def test_max_keys_drops_new_undeclared_key(self):
        set_telemetry_config("TELEMETRY_MAX_KEYS", len(T.TELEMETRY_KEYS))
        self.assertFalse(T.telemetry("x.new"))
        self.assertTrue(T.telemetry("auth.login"))

    def test_write_error_never_raises(self):
        with mock.patch("app_data.telemetry._write_raw", side_effect=RuntimeError("db down")):
            self.assertFalse(T.telemetry("auth.login"))
