"""
Testy `_wyciągnij_koszt_usd` z `KlientPayPerQ`.

Mockujemy `usage` jako pydantic-podobny obiekt: prawdziwy adapter dostaje
`Message.usage` z Anthropic SDK z `model_extra` zawierającym dodatki PPQ.
Tutaj symulujemy oba kształty (extras i native attrs) — żeby zachowanie
było odporne na ewolucję SDK.
"""

from __future__ import annotations

import os
import sys
import unittest
from types import SimpleNamespace

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

from sdk.adaptery.payperq import (  # noqa: E402
    KlientPayPerQ,
    _PayPerQAnthropic,
    _PayPerQOpenAI,
    _wyciągnij_koszt_usd,
    is_anthropic_compatible,
)


def _usage(extras: dict | None = None, **native) -> SimpleNamespace:
    """Pydantic-like usage: native atrybuty + model_extra dla pól PPQ."""
    obj = SimpleNamespace(**native)
    obj.model_extra = extras or {}
    return obj


class TestWyciągnijKosztUsd(unittest.TestCase):

    def test_brak_usage_zwraca_none(self):
        self.assertIsNone(_wyciągnij_koszt_usd(None))

    def test_usage_cost_dodatni_brany_wprost(self):
        u = _usage(extras={"cost": 0.01234})
        self.assertEqual(_wyciągnij_koszt_usd(u), 0.01234)

    def test_native_cost_atrybut_tez_dziala(self):
        # Gdyby SDK kiedyś podniósł `cost` do native field
        u = SimpleNamespace(cost=0.005, model_extra={})
        self.assertEqual(_wyciągnij_koszt_usd(u), 0.005)

    def test_byok_cost_zero_fallback_na_upstream(self):
        # Realny case dla naszego klucza payperq (is_byok=true)
        u = _usage(extras={
            "cost": 0,
            "cost_details": {
                "upstream_inference_cost": 0.00025,
                "upstream_inference_prompt_cost": 0.0001,
                "upstream_inference_completions_cost": 0.00015,
            },
        })
        self.assertEqual(_wyciągnij_koszt_usd(u), 0.00025)

    def test_cost_details_jako_pydantic_z_extras(self):
        # cost_details może też być pydantic-em, nie dict-em
        cd = SimpleNamespace(
            upstream_inference_cost=0.0007,
            model_extra={"upstream_inference_prompt_cost": 0.0003},
        )
        u = _usage(extras={"cost": 0, "cost_details": cd})
        self.assertEqual(_wyciągnij_koszt_usd(u), 0.0007)

    def test_oba_zerowe_zwraca_none(self):
        u = _usage(extras={
            "cost": 0,
            "cost_details": {"upstream_inference_cost": 0},
        })
        self.assertIsNone(_wyciągnij_koszt_usd(u))

    def test_brak_pol_zwraca_none(self):
        # Czysty Anthropic-style usage bez ppq-extras
        u = _usage()
        self.assertIsNone(_wyciągnij_koszt_usd(u))


class TestIsAnthropicCompatible(unittest.TestCase):

    def test_claude_z_myslnikiem(self):
        self.assertTrue(is_anthropic_compatible("claude-opus-4-7"))
        self.assertTrue(is_anthropic_compatible("claude-haiku-4-5"))

    def test_claude_z_kropka(self):
        # PPQ catalog może zwracać i tę formę — dispatch wciąż działa
        self.assertTrue(is_anthropic_compatible("claude-opus-4.7"))

    def test_anthropic_floating_alias(self):
        self.assertTrue(is_anthropic_compatible("~anthropic/claude-haiku-latest"))

    def test_openai_modele(self):
        self.assertFalse(is_anthropic_compatible("gpt-5.5-pro"))
        self.assertFalse(is_anthropic_compatible("gpt-4o-mini"))

    def test_inni_provider_idzie_openai(self):
        self.assertFalse(is_anthropic_compatible("qwen/qwen3.5-plus-20260420"))
        self.assertFalse(is_anthropic_compatible("mistralai/mistral-small-2603"))
        self.assertFalse(is_anthropic_compatible("gemini-3-flash-preview"))

    def test_pusty_lub_none(self):
        self.assertFalse(is_anthropic_compatible(""))
        self.assertFalse(is_anthropic_compatible("   "))


class TestKlientPayPerQDispatch(unittest.TestCase):

    def test_claude_idzie_do_anthropic(self):
        k = KlientPayPerQ("test-key", "claude-opus-4-7")
        self.assertIsInstance(k._aktywny(), _PayPerQAnthropic)

    def test_gpt_idzie_do_openai(self):
        k = KlientPayPerQ("test-key", "gpt-5.5-pro")
        self.assertIsInstance(k._aktywny(), _PayPerQOpenAI)

    def test_set_model_zmienia_aktywny_transport(self):
        # Session.set_model zmienia self._model na warstwie KlientPayPerQ;
        # dispatcher musi to zauważyć i przepiąć aktywny transport.
        k = KlientPayPerQ("test-key", "claude-opus-4-7")
        self.assertIsInstance(k._aktywny(), _PayPerQAnthropic)
        k._model = "gpt-5.5-pro"
        self.assertIsInstance(k._aktywny(), _PayPerQOpenAI)
        k._model = "claude-haiku-4-5"
        self.assertIsInstance(k._aktywny(), _PayPerQAnthropic)

    def test_set_model_propaguje_do_subadapterow(self):
        # _aktywny() syncuje self._model do podadapterów — kluczowe, bo to
        # ICH `_model` faktycznie ląduje w `messages.create(model=...)`.
        k = KlientPayPerQ("test-key", "claude-opus-4-7")
        k._model = "claude-haiku-4-5"
        k._aktywny()
        self.assertEqual(k._anthropic._model, "claude-haiku-4-5")
        self.assertEqual(k._openai._model, "claude-haiku-4-5")


if __name__ == "__main__":
    unittest.main()
