"""
Trust-critical tests: verify that usage extraction, memoization flagging,
and telemetry reporting are NEVER misleading.

These tests exist because a bug in _extract_usage caused ALL Anthropic/OpenAI/Google
SDK calls to report zero usage, which the server interpreted as memoized — creating
phantom savings that didn't exist. This is a customer trust issue.

Every test in this file protects against misrepresentation of:
- What the customer actually paid (optimized cost)
- What we saved them (savings)
- Whether memoization actually happened
"""

import os
import sys
import time
import tempfile
import uuid

sys.path.insert(0, os.path.join(os.path.dirname(__file__), ".."))

from genosis import Genosis
from genosis.client import _sha256


_test_counter = 0


def make_client(**kwargs) -> Genosis:
    global _test_counter
    _test_counter += 1
    defaults = {
        "api_key": "gns_test_abc123",
        "base_url": "http://localhost:3001",
        "manifest_refresh_interval": 0,
        "buffer_path": os.path.join(tempfile.gettempdir(), f"genosis_trust_test_{os.getpid()}_{_test_counter}.db"),
    }
    defaults.update(kwargs)
    g = Genosis(**defaults)
    g._worker.shutdown(timeout=0.5)
    return g


def capture_buffer(g: Genosis) -> list:
    events = g._buffer.peek(1000)
    return [{"type": e["type"], "payload": e["payload"]} for e in events]


def get_telemetry_events(g: Genosis) -> list:
    return [e for e in capture_buffer(g) if e["type"] == "telemetry"]


# ── Pydantic model simulators ──────────────────────────────────────────────
# These simulate the actual SDK response objects from Anthropic, OpenAI, and Google.


class AnthropicMessage:
    """Simulates anthropic.types.Message (Pydantic BaseModel)."""
    def __init__(self, input_tokens=100, output_tokens=50, cache_write=0, cache_read=0):
        self.id = "msg_test"
        self.type = "message"
        self.role = "assistant"
        self.content = [{"type": "text", "text": "test response"}]
        self.model = "claude-sonnet-4-6"
        self.stop_reason = "end_turn"
        self.usage = type("Usage", (), {
            "input_tokens": input_tokens,
            "output_tokens": output_tokens,
            "cache_creation_input_tokens": cache_write,
            "cache_read_input_tokens": cache_read,
            "cache_creation": None,
            "server_tool_use": None,
            "service_tier": None,
        })()

    def model_dump(self):
        return {
            "id": self.id, "type": self.type, "role": self.role,
            "content": self.content, "model": self.model,
            "stop_reason": self.stop_reason,
            "usage": {
                "input_tokens": self.usage.input_tokens,
                "output_tokens": self.usage.output_tokens,
                "cache_creation_input_tokens": self.usage.cache_creation_input_tokens,
                "cache_read_input_tokens": self.usage.cache_read_input_tokens,
            },
        }


class OpenAIChatCompletion:
    """Simulates openai.types.chat.ChatCompletion (Pydantic BaseModel)."""
    def __init__(self, prompt_tokens=200, completion_tokens=80, cached_tokens=50):
        self.id = "chatcmpl-test"
        self.object = "chat.completion"
        self.choices = [{"message": {"role": "assistant", "content": "test"}}]
        self.usage = type("Usage", (), {
            "prompt_tokens": prompt_tokens,
            "completion_tokens": completion_tokens,
            "prompt_tokens_details": type("Details", (), {"cached_tokens": cached_tokens})(),
            "total_tokens": prompt_tokens + completion_tokens,
        })()

    def model_dump(self):
        return {
            "id": self.id, "object": self.object, "choices": self.choices,
            "usage": {
                "prompt_tokens": self.usage.prompt_tokens,
                "completion_tokens": self.usage.completion_tokens,
                "prompt_tokens_details": {"cached_tokens": self.usage.prompt_tokens_details.cached_tokens},
                "total_tokens": self.usage.total_tokens,
            },
        }


class GoogleGenerateContentResponse:
    """Simulates google.genai GenerateContentResponse (Pydantic BaseModel)."""
    def __init__(self, prompt_tokens=300, candidate_tokens=60, cached_tokens=100):
        self.text = "test response"
        self.candidates = [{"content": {"parts": [{"text": "test"}]}}]
        self.usage_metadata = type("Meta", (), {
            "promptTokenCount": prompt_tokens,
            "candidatesTokenCount": candidate_tokens,
            "cachedContentTokenCount": cached_tokens,
        })()

    def model_dump(self):
        return {
            "text": self.text,
            "candidates": self.candidates,
            "usageMetadata": {
                "promptTokenCount": self.usage_metadata.promptTokenCount,
                "candidatesTokenCount": self.usage_metadata.candidatesTokenCount,
                "cachedContentTokenCount": self.usage_metadata.cachedContentTokenCount,
            },
        }


class GoogleLegacyResponse:
    """Simulates google.generativeai GenerateContentResponse (has to_dict, not model_dump)."""
    def __init__(self, prompt_tokens=300, candidate_tokens=60, cached_tokens=100):
        self._prompt = prompt_tokens
        self._candidate = candidate_tokens
        self._cached = cached_tokens

    def to_dict(self):
        return {
            "usageMetadata": {
                "promptTokenCount": self._prompt,
                "candidatesTokenCount": self._candidate,
                "cachedContentTokenCount": self._cached,
            },
        }


# ── Usage Extraction: SDK Response Objects ──────────────────────────────────

class TestUsageExtractionFromSDKObjects:
    """Verify that SDK response objects from ALL providers produce correct non-zero usage."""

    def test_anthropic_pydantic_model(self):
        g = make_client()
        usage = g._extract_usage(AnthropicMessage(input_tokens=500, output_tokens=200, cache_write=100, cache_read=300), "anthropic")
        assert usage["input"] == 500
        assert usage["output"] == 200
        assert usage["cache_write"] == 100
        assert usage["cache_read"] == 300

    def test_anthropic_non_zero_output(self):
        """A real Anthropic call MUST produce non-zero output tokens."""
        g = make_client()
        usage = g._extract_usage(AnthropicMessage(input_tokens=100, output_tokens=50), "anthropic")
        assert usage["output"] > 0, "Real Anthropic call should have non-zero output tokens"
        assert usage["input"] > 0, "Real Anthropic call should have non-zero input tokens"

    def test_openai_pydantic_model(self):
        g = make_client()
        usage = g._extract_usage(OpenAIChatCompletion(prompt_tokens=400, completion_tokens=100, cached_tokens=200), "openai")
        assert usage["input"] == 400
        assert usage["output"] == 100
        assert usage["cache_read"] == 200

    def test_openai_non_zero_output(self):
        """A real OpenAI call MUST produce non-zero output tokens."""
        g = make_client()
        usage = g._extract_usage(OpenAIChatCompletion(prompt_tokens=200, completion_tokens=80), "openai")
        assert usage["output"] > 0, "Real OpenAI call should have non-zero output tokens"
        assert usage["input"] > 0, "Real OpenAI call should have non-zero input tokens"

    def test_google_new_sdk_pydantic(self):
        g = make_client()
        usage = g._extract_usage(GoogleGenerateContentResponse(prompt_tokens=300, candidate_tokens=60, cached_tokens=100), "google")
        assert usage["input"] == 300
        assert usage["output"] == 60
        assert usage["cache_read"] == 100

    def test_google_legacy_sdk_to_dict(self):
        g = make_client()
        usage = g._extract_usage(GoogleLegacyResponse(prompt_tokens=300, candidate_tokens=60, cached_tokens=100), "google")
        assert usage["input"] == 300
        assert usage["output"] == 60
        assert usage["cache_read"] == 100

    def test_google_non_zero_output(self):
        """A real Google call MUST produce non-zero output tokens."""
        g = make_client()
        usage = g._extract_usage(GoogleGenerateContentResponse(prompt_tokens=300, candidate_tokens=60), "google")
        assert usage["output"] > 0, "Real Google call should have non-zero output tokens"
        assert usage["input"] > 0, "Real Google call should have non-zero input tokens"


# ── Memoization Flag Correctness ────────────────────────────────────────────

class TestMemoizationFlagCorrectness:
    """Verify that the memoized flag is ONLY true when memoization actually happened."""

    def test_normal_call_flags_not_memoized(self):
        """A normal LLM call must flag memoized=false in telemetry."""
        g = make_client()
        response = AnthropicMessage(input_tokens=100, output_tokens=50)
        g._queue_telemetry(
            {"model": "claude-sonnet-4-6", "system": "hello", "messages": []},
            response,
        )
        events = get_telemetry_events(g)
        assert len(events) == 1
        assert events[0]["payload"]["memoized"] is False

    def test_normal_call_has_nonzero_usage(self):
        """A normal LLM call must have non-zero usage in telemetry."""
        g = make_client()
        response = AnthropicMessage(input_tokens=100, output_tokens=50)
        g._queue_telemetry(
            {"model": "claude-sonnet-4-6", "system": "hello", "messages": []},
            response,
        )
        events = get_telemetry_events(g)
        usage = events[0]["payload"]["usage"]
        assert usage["input_tokens"] > 0 or usage["output_tokens"] > 0, \
            "Normal call telemetry must have non-zero usage"

    def test_memoized_call_flags_memoized(self):
        """A memoized call must flag memoized=true in telemetry."""
        g = make_client()
        g._queue_memoized_telemetry(
            {"model": "claude-sonnet-4-6", "system": "hello", "messages": []},
            [{"hash": "abc", "tokens": 100, "cached": True, "position": 0}],
            "fp_xyz", 50,
        )
        events = get_telemetry_events(g)
        assert len(events) == 1
        assert events[0]["payload"]["memoized"] is True

    def test_memoized_call_has_zero_usage(self):
        """A memoized call must have zero usage — no LLM call was made."""
        g = make_client()
        g._queue_memoized_telemetry(
            {"model": "claude-sonnet-4-6", "system": "hello", "messages": []},
            [], "fp_xyz", 50,
        )
        events = get_telemetry_events(g)
        usage = events[0]["payload"]["usage"]
        assert usage["input_tokens"] == 0
        assert usage["output_tokens"] == 0
        assert usage["cache_write_tokens"] == 0
        assert usage["cache_read_tokens"] == 0


# ── End-to-End Call Flow ────────────────────────────────────────────────────

class TestEndToEndCallFlowTrust:
    """Verify the complete g.call() → telemetry → server interpretation chain."""

    def test_anthropic_call_produces_nonzero_telemetry(self):
        """g.call() with Anthropic SDK object produces telemetry with real usage."""
        g = make_client()
        response = AnthropicMessage(input_tokens=500, output_tokens=200, cache_write=100, cache_read=300)
        result = g.call(
            {"model": "claude-sonnet-4-6", "system": "test prompt", "messages": [{"role": "user", "content": "hi"}]},
            lambda p: response,
        )
        assert result.memoized is False

        events = get_telemetry_events(g)
        assert len(events) == 1
        payload = events[0]["payload"]
        assert payload["memoized"] is False
        assert payload["usage"]["input_tokens"] == 500
        assert payload["usage"]["output_tokens"] == 200
        assert payload["usage"]["cache_write_tokens"] == 100
        assert payload["usage"]["cache_read_tokens"] == 300

    def test_openai_call_produces_nonzero_telemetry(self):
        """g.call() with OpenAI SDK object produces telemetry with real usage."""
        g = make_client()
        response = OpenAIChatCompletion(prompt_tokens=400, completion_tokens=100, cached_tokens=50)
        result = g.call(
            {"model": "gpt-4o", "messages": [{"role": "system", "content": "test"}, {"role": "user", "content": "hi"}]},
            lambda p: response,
        )
        assert result.memoized is False

        events = get_telemetry_events(g)
        assert len(events) == 1
        payload = events[0]["payload"]
        assert payload["memoized"] is False
        assert payload["usage"]["input_tokens"] == 400
        assert payload["usage"]["output_tokens"] == 100

    def test_google_call_produces_nonzero_telemetry(self):
        """g.call() with Google SDK object produces telemetry with real usage."""
        g = make_client()
        response = GoogleGenerateContentResponse(prompt_tokens=300, candidate_tokens=60, cached_tokens=100)
        result = g.call(
            {"model": "gemini-2.5-flash", "systemInstruction": {"parts": [{"text": "test"}]}, "contents": [{"role": "user", "parts": [{"text": "hi"}]}]},
            lambda p: response,
        )
        assert result.memoized is False

        events = get_telemetry_events(g)
        assert len(events) == 1
        payload = events[0]["payload"]
        assert payload["memoized"] is False
        assert payload["usage"]["input_tokens"] == 300
        assert payload["usage"]["output_tokens"] == 60

    def test_fallback_on_error_does_not_produce_telemetry(self):
        """When g.call() falls back due to an error, NO telemetry should be queued."""
        g = make_client()

        def failing_fn(p):
            raise Exception("LLM provider error")

        # This should NOT produce telemetry — the call failed
        try:
            g.call(
                {"model": "claude-sonnet-4-6", "system": "test", "messages": [{"role": "user", "content": "hi"}]},
                failing_fn,
            )
        except:
            pass

        events = get_telemetry_events(g)
        # The fallback path calls fn(params) which also throws,
        # so the outer except catches it. No telemetry should be queued.
        for event in events:
            assert event["payload"].get("memoized") is not True or event["payload"]["usage"]["input_tokens"] == 0, \
                "Fallback path must not produce false memoization telemetry"


# ── Server Interpretation Simulation ────────────────────────────────────────

class TestServerInterpretation:
    """Simulate what the server would do with the telemetry events we produce."""

    @staticmethod
    def server_would_mark_memoized(event_payload: dict) -> bool:
        """Replicate the server's memoization detection logic."""
        # New logic: trust explicit flag, fall back to zero-usage inference
        if event_payload.get("memoized") is True:
            return True
        if event_payload.get("memoized") is False:
            return False
        # Backward compat: infer from zero usage
        u = event_payload.get("usage", {})
        return (u.get("input_tokens", 0) == 0
                and u.get("output_tokens", 0) == 0
                and u.get("cache_write_tokens", 0) == 0
                and u.get("cache_read_tokens", 0) == 0)

    def test_normal_anthropic_not_marked_memoized(self):
        g = make_client()
        g._queue_telemetry(
            {"model": "claude-sonnet-4-6", "system": "test", "messages": []},
            AnthropicMessage(input_tokens=100, output_tokens=50),
        )
        events = get_telemetry_events(g)
        assert not self.server_would_mark_memoized(events[0]["payload"])

    def test_normal_openai_not_marked_memoized(self):
        g = make_client()
        g._queue_telemetry(
            {"model": "gpt-4o", "messages": [{"role": "system", "content": "x"}, {"role": "user", "content": "y"}]},
            OpenAIChatCompletion(prompt_tokens=200, completion_tokens=80),
        )
        events = get_telemetry_events(g)
        assert not self.server_would_mark_memoized(events[0]["payload"])

    def test_normal_google_not_marked_memoized(self):
        g = make_client()
        g._queue_telemetry(
            {"model": "gemini-2.5-flash", "systemInstruction": {"parts": [{"text": "x"}]}, "contents": []},
            GoogleGenerateContentResponse(prompt_tokens=300, candidate_tokens=60),
        )
        events = get_telemetry_events(g)
        assert not self.server_would_mark_memoized(events[0]["payload"])

    def test_actual_memoization_is_marked(self):
        g = make_client()
        g._queue_memoized_telemetry(
            {"model": "claude-sonnet-4-6", "system": "test", "messages": []},
            [], "fp_test", 50,
        )
        events = get_telemetry_events(g)
        assert self.server_would_mark_memoized(events[0]["payload"])

    def test_explicit_false_overrides_zero_usage(self):
        """Even if usage is all zeros, explicit memoized=false should NOT be marked memoized."""
        payload = {
            "memoized": False,
            "usage": {"input_tokens": 0, "output_tokens": 0, "cache_write_tokens": 0, "cache_read_tokens": 0},
        }
        assert not self.server_would_mark_memoized(payload)


# ── Edge Cases That Could Create Phantom Savings ────────────────────────────

class TestPhantomSavingsProtection:
    """Test edge cases that could falsely inflate savings."""

    def test_empty_response_dict_not_memoized(self):
        """An empty dict response (edge case) should not be marked memoized."""
        g = make_client()
        g._queue_telemetry(
            {"model": "claude-sonnet-4-6", "system": "test", "messages": []},
            {},  # Empty dict — broken response
        )
        events = get_telemetry_events(g)
        assert events[0]["payload"]["memoized"] is False

    def test_none_response_handled(self):
        """None response should not crash or produce false memoization."""
        g = make_client()
        g._queue_telemetry(
            {"model": "claude-sonnet-4-6", "system": "test", "messages": []},
            None,
        )
        events = get_telemetry_events(g)
        # Should still queue telemetry with zero usage but memoized=false
        assert events[0]["payload"]["memoized"] is False

    def test_response_with_empty_usage_not_memoized(self):
        """Response with empty usage object should not be marked memoized."""
        g = make_client()
        g._queue_telemetry(
            {"model": "claude-sonnet-4-6", "system": "test", "messages": []},
            {"usage": {}},
        )
        events = get_telemetry_events(g)
        assert events[0]["payload"]["memoized"] is False

    def test_response_with_null_usage_not_memoized(self):
        """Response with null usage should not be marked memoized."""
        g = make_client()
        g._queue_telemetry(
            {"model": "claude-sonnet-4-6", "system": "test", "messages": []},
            {"usage": None},
        )
        events = get_telemetry_events(g)
        assert events[0]["payload"]["memoized"] is False

    def test_string_response_not_memoized(self):
        """String response (total edge case) should not be marked memoized."""
        g = make_client()
        g._queue_telemetry(
            {"model": "claude-sonnet-4-6", "system": "test", "messages": []},
            "not a real response",
        )
        events = get_telemetry_events(g)
        assert events[0]["payload"]["memoized"] is False

    def test_object_without_model_dump_not_memoized(self):
        """Unknown object type should produce zero usage but memoized=false."""
        g = make_client()

        class WeirdResponse:
            pass

        g._queue_telemetry(
            {"model": "claude-sonnet-4-6", "system": "test", "messages": []},
            WeirdResponse(),
        )
        events = get_telemetry_events(g)
        assert events[0]["payload"]["memoized"] is False
