"""
Unit tests for server/ai_provider.py — the shared AI provider dispatch used
by both site_chat.py and ca_assistant.py. Only exercises provider selection
and error mapping; the actual HTTP calls need real API keys and are out of
scope for a unit test.

Run with: cd server && ../venv/bin/python3 -m unittest tests.test_ai_provider -v
"""
import os
import sys
import tempfile
import unittest

sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))


class ProviderSelectionTests(unittest.TestCase):
    def setUp(self):
        for key in ("GROQ_API_KEY", "ANTHROPIC_API_KEY", "CA_AI_PROVIDER"):
            os.environ.pop(key, None)
        # ai_provider.py now queries the DB (ai_provider_key) before falling
        # back to env vars, so it needs a real, migrated DB — same fresh-tmpdir
        # trick as test_site_chat.py, since a bare `import ai_provider` at
        # collection time would otherwise bind it to whatever db module (and
        # whatever database file) happened to be live at that point.
        self._tmpdir = tempfile.mkdtemp()
        os.environ["CA_DATA_DIR"] = self._tmpdir
        for mod in ("db", "ai_provider"):
            sys.modules.pop(mod, None)
        import db as db_mod
        global db, ai_provider
        db = db_mod
        import ai_provider as ai_provider_mod
        ai_provider = ai_provider_mod
        db.init_db()

    def tearDown(self):
        for key in ("GROQ_API_KEY", "ANTHROPIC_API_KEY", "CA_AI_PROVIDER"):
            os.environ.pop(key, None)

    def test_no_keys_means_not_configured(self):
        self.assertIsNone(ai_provider.active_provider())
        self.assertEqual(ai_provider.status(), {"configured": False, "provider": None, "model": None})

    def test_groq_key_alone_selects_groq(self):
        os.environ["GROQ_API_KEY"] = "x"
        self.assertEqual(ai_provider.active_provider(), "groq")
        status = ai_provider.status()
        self.assertTrue(status["configured"])
        self.assertEqual(status["provider"], "groq")
        self.assertIsNotNone(status["model"])

    def test_anthropic_key_alone_selects_anthropic(self):
        os.environ["ANTHROPIC_API_KEY"] = "x"
        self.assertEqual(ai_provider.active_provider(), "anthropic")

    def test_groq_wins_when_both_present_free_tier_first(self):
        os.environ["GROQ_API_KEY"] = "x"
        os.environ["ANTHROPIC_API_KEY"] = "y"
        self.assertEqual(ai_provider.active_provider(), "groq")

    def test_ca_ai_provider_overrides_priority(self):
        os.environ["GROQ_API_KEY"] = "x"
        os.environ["ANTHROPIC_API_KEY"] = "y"
        os.environ["CA_AI_PROVIDER"] = "anthropic"
        self.assertEqual(ai_provider.active_provider(), "anthropic")

    def test_forced_provider_without_its_key_is_not_configured(self):
        os.environ["GROQ_API_KEY"] = "x"
        os.environ["CA_AI_PROVIDER"] = "anthropic"   # forced, but no ANTHROPIC_API_KEY set
        self.assertIsNone(ai_provider.active_provider())

    def test_call_raises_not_configured_with_no_keys(self):
        with self.assertRaises(ai_provider.ProviderError) as ctx:
            ai_provider.call([{"role": "user", "content": "hi"}], "system", 100)
        self.assertEqual(str(ctx.exception), "not_configured")

    def test_model_names_are_env_overridable(self):
        os.environ["GROQ_API_KEY"] = "x"
        os.environ["CA_AI_GROQ_MODEL"] = "some-other-model"
        try:
            self.assertEqual(ai_provider.status()["model"], "some-other-model")
        finally:
            os.environ.pop("CA_AI_GROQ_MODEL", None)


class AdminKeyManagementTests(ProviderSelectionTests):
    """Reuses ProviderSelectionTests' setUp/tearDown (fresh DB + clean env)."""

    def test_set_key_makes_it_active_and_masks_on_list(self):
        ai_provider.set_key(1, "groq", "gsk_real_secret_value")
        rows = ai_provider.list_keys(1)
        self.assertEqual(len(rows), 1)
        self.assertEqual(rows[0]["provider"], "groq")
        self.assertTrue(rows[0]["is_active"])
        self.assertNotIn("api_key_enc", rows[0])
        self.assertNotIn("gsk_real_secret_value", json_dump(rows[0]))

    def test_db_key_wins_over_env_var(self):
        os.environ["ANTHROPIC_API_KEY"] = "env-key"
        ai_provider.set_key(1, "groq", "db-key")
        self.assertEqual(ai_provider.active_provider(), "groq")

    def test_setting_a_second_key_active_deactivates_the_first(self):
        ai_provider.set_key(1, "groq", "key-a", make_active=True)
        ai_provider.set_key(1, "anthropic", "key-b", make_active=True)
        rows = {r["provider"]: r["is_active"] for r in ai_provider.list_keys(1)}
        self.assertFalse(rows["groq"])
        self.assertTrue(rows["anthropic"])
        self.assertEqual(ai_provider.active_provider(), "anthropic")

    def test_set_active_switches_without_touching_keys(self):
        ai_provider.set_key(1, "groq", "key-a", make_active=True)
        ai_provider.set_key(1, "anthropic", "key-b", make_active=False)
        self.assertEqual(ai_provider.active_provider(), "groq")
        ai_provider.set_active(1, "anthropic")
        self.assertEqual(ai_provider.active_provider(), "anthropic")

    def test_set_active_on_unknown_provider_raises(self):
        with self.assertRaises(ai_provider.ProviderError):
            ai_provider.set_active(1, "groq")   # never configured

    def test_delete_key_falls_back_to_env(self):
        os.environ["ANTHROPIC_API_KEY"] = "env-key"
        ai_provider.set_key(1, "groq", "db-key")
        self.assertEqual(ai_provider.active_provider(), "groq")
        ai_provider.delete_key(1, "groq")
        self.assertEqual(ai_provider.active_provider(), "anthropic")

    def test_delete_key_with_nothing_configured_leaves_unconfigured(self):
        ai_provider.set_key(1, "groq", "db-key")
        ai_provider.delete_key(1, "groq")
        self.assertIsNone(ai_provider.active_provider())
        self.assertEqual(ai_provider.list_keys(1), [])

    def test_set_key_rejects_unknown_provider(self):
        with self.assertRaises(ai_provider.ProviderError):
            ai_provider.set_key(1, "not-a-real-provider", "x")

    def test_set_key_rejects_empty_api_key(self):
        with self.assertRaises(ai_provider.ProviderError):
            ai_provider.set_key(1, "groq", "   ")

    def test_replacing_a_key_updates_in_place_not_duplicates(self):
        ai_provider.set_key(1, "groq", "first-key")
        ai_provider.set_key(1, "groq", "second-key")
        rows = ai_provider.list_keys(1)
        self.assertEqual(len(rows), 1)

    def test_available_providers_matches_implemented_callers(self):
        self.assertEqual(set(ai_provider.available_providers()), set(ai_provider._CALLERS.keys()))


def json_dump(d):
    import json
    return json.dumps(d)


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