"""
Testy `Session.charge_for_usd` / `add_sats` / `set_model`.

Używamy fake-Agent (tylko z atrybutem `llm._model`), bo prawdziwy `Agent`
wymagałby pełnego bootstrap-u SharedRuntime i gateway-a.
"""

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__), "..", ".."))
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "..", "jou"))

from mcp_nostr_explorer import billing  # noqa: E402
from mcp_nostr_explorer.agent_runtime import Session  # noqa: E402


def _fake_agent(model: str = "claude-opus-4-7") -> SimpleNamespace:
    return SimpleNamespace(llm=SimpleNamespace(_model=model))


class TestSessionBilling(unittest.IsolatedAsyncioTestCase):

    async def asyncSetUp(self):
        billing.set_btc_usd_rate(80_000.0)

    async def asyncTearDown(self):
        billing.set_btc_usd_rate(None)

    async def test_charge_odejmuje_sat_emituje_event(self):
        sess = Session(sid="abc", agent=_fake_agent(), balance_sat=10000)
        evt = await sess.charge_for_usd(0.01)  # → 12 sat przy 80k
        self.assertEqual(sess.balance_sat, 9988)
        self.assertEqual(evt, {
            "type": "balance",
            "sat": 9988,
            "last_charge_sat": 12,
            "last_charge_usd": 0.01,
        })

    async def test_charge_zero_usd_nie_zmienia_salda(self):
        sess = Session(sid="x", agent=_fake_agent(), balance_sat=500)
        evt = await sess.charge_for_usd(0.0)
        self.assertEqual(sess.balance_sat, 500)
        self.assertEqual(evt["last_charge_sat"], 0)

    async def test_charge_powyzej_salda_zatrzymuje_na_zero(self):
        # Brak ujemnego salda — kolejne tury powinny być blokowane wcześniej,
        # ale tej już-wykonanej nie cofniemy. Schodzimy do 0 i koniec.
        sess = Session(sid="y", agent=_fake_agent(), balance_sat=10)
        evt = await sess.charge_for_usd(1.0)  # by chciało 1250 sat
        self.assertEqual(sess.balance_sat, 0)
        self.assertEqual(evt["last_charge_sat"], 10)

    async def test_balance_zero_blokuje_kolejna_ture(self):
        # Po wyzerowaniu salda WS-handler powinien blokować kolejne `run`.
        # Tutaj weryfikujemy stan w obiekcie sesji — sam handler jest testowany
        # smoke-testem WS.
        sess = Session(sid="z", agent=_fake_agent(), balance_sat=10)
        await sess.charge_for_usd(1.0)
        self.assertEqual(sess.balance_sat, 0)
        self.assertLessEqual(sess.balance_sat, 0)

    async def test_add_sats_zwieksza_saldo(self):
        sess = Session(sid="z", agent=_fake_agent(), balance_sat=100)
        evt = sess.add_sats(2500)
        self.assertEqual(sess.balance_sat, 2600)
        self.assertEqual(evt["last_charge_sat"], -2500)

    async def test_add_sats_ujemne_lub_zero_rzuca(self):
        sess = Session(sid="z", agent=_fake_agent(), balance_sat=100)
        with self.assertRaises(ValueError):
            sess.add_sats(0)
        with self.assertRaises(ValueError):
            sess.add_sats(-50)


class TestSessionModel(unittest.TestCase):

    def test_model_czyta_z_llm(self):
        sess = Session(sid="m", agent=_fake_agent("foo-bar-1"))
        self.assertEqual(sess.model(), "foo-bar-1")

    def test_set_model_zmienia(self):
        sess = Session(sid="m", agent=_fake_agent("foo-bar-1"))
        sess.set_model("baz-qux-2")
        self.assertEqual(sess.agent.llm._model, "baz-qux-2")
        self.assertEqual(sess.model(), "baz-qux-2")

    def test_set_model_strip(self):
        sess = Session(sid="m", agent=_fake_agent())
        sess.set_model("  spacja-przed-i-po  ")
        self.assertEqual(sess.model(), "spacja-przed-i-po")

    def test_set_model_pusty_rzuca(self):
        sess = Session(sid="m", agent=_fake_agent())
        with self.assertRaises(ValueError):
            sess.set_model("")
        with self.assertRaises(ValueError):
            sess.set_model("   ")
        with self.assertRaises(ValueError):
            sess.set_model(None)


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