"""
Unit tests for server/site_chat.py — the public landing-page chatbot backend.
Exercises the DB-backed pure logic (conversation lifecycle, lead capture,
site-context building, rate limiting) against a throwaway in-memory-style
DB. Does NOT exercise _call_provider — that needs a real ANTHROPIC_API_KEY
and is out of scope for a unit test.

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

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


class SiteChatTests(unittest.TestCase):
    def setUp(self):
        self._tmpdir = tempfile.mkdtemp()
        os.environ["CA_DATA_DIR"] = self._tmpdir
        # db.py reads CA_DATA_DIR at import time, so force a fresh import per
        # test — ai_provider must be popped too, or site_chat's `import
        # ai_provider` just rebinds the stale module from collection time,
        # whose own `db` reference still points at the old database.
        for mod in ("db", "ai_provider", "site_chat"):
            sys.modules.pop(mod, None)
        import db as db_mod
        global db, site_chat
        db = db_mod
        import site_chat as site_chat_mod
        site_chat = site_chat_mod
        db.init_db()

    def test_status_reports_unconfigured_without_key(self):
        os.environ.pop("ANTHROPIC_API_KEY", None)
        os.environ.pop("GROQ_API_KEY", None)
        os.environ.pop("CA_AI_PROVIDER", None)
        status = site_chat.status()
        self.assertFalse(status["configured"])
        self.assertIsNone(status["provider"])

    def test_groq_free_tier_preferred_when_both_keys_present(self):
        os.environ["GROQ_API_KEY"] = "test-groq-key"
        os.environ["ANTHROPIC_API_KEY"] = "test-anthropic-key"
        os.environ.pop("CA_AI_PROVIDER", None)
        try:
            status = site_chat.status()
            self.assertTrue(status["configured"])
            self.assertEqual(status["provider"], "groq")
        finally:
            os.environ.pop("GROQ_API_KEY", None)
            os.environ.pop("ANTHROPIC_API_KEY", None)

    def test_ca_ai_provider_forces_a_specific_provider(self):
        os.environ["ANTHROPIC_API_KEY"] = "test-anthropic-key"
        os.environ["CA_AI_PROVIDER"] = "anthropic"
        try:
            status = site_chat.status()
            self.assertTrue(status["configured"])
            self.assertEqual(status["provider"], "anthropic")
        finally:
            os.environ.pop("ANTHROPIC_API_KEY", None)
            os.environ.pop("CA_AI_PROVIDER", None)

    def test_conversation_reused_for_same_session(self):
        tok = site_chat.new_session_token()
        c1 = site_chat._get_or_create_conversation(1, tok)
        c2 = site_chat._get_or_create_conversation(1, tok)
        self.assertEqual(c1["id"], c2["id"])

    def test_different_sessions_get_different_conversations(self):
        c1 = site_chat._get_or_create_conversation(1, site_chat.new_session_token())
        c2 = site_chat._get_or_create_conversation(1, site_chat.new_session_token())
        self.assertNotEqual(c1["id"], c2["id"])

    def test_reply_without_api_key_raises_but_stores_user_message(self):
        os.environ.pop("ANTHROPIC_API_KEY", None)
        tok = site_chat.new_session_token()
        with self.assertRaises(site_chat.ChatError):
            site_chat.reply(1, tok, "What services do you offer?")
        conv = site_chat._get_or_create_conversation(1, tok)
        msgs = db.query("SELECT * FROM chatbot_message WHERE conversation_id = ?", (conv["id"],))
        self.assertEqual(len(msgs), 1)
        self.assertEqual(msgs[0]["role"], "user")

    def test_reply_rejects_empty_message(self):
        with self.assertRaises(site_chat.ChatError):
            site_chat.reply(1, site_chat.new_session_token(), "   ")

    def test_reply_rejects_overlong_message(self):
        with self.assertRaises(site_chat.ChatError):
            site_chat.reply(1, site_chat.new_session_token(), "x" * 2000)

    def test_lead_capture_requires_valid_email(self):
        tok = site_chat.new_session_token()
        with self.assertRaises(site_chat.ChatError):
            site_chat.capture_lead(1, tok, "Ram", "not-an-email", "", "")

    def test_lead_capture_requires_name(self):
        tok = site_chat.new_session_token()
        with self.assertRaises(site_chat.ChatError):
            site_chat.capture_lead(1, tok, "", "ram@example.com", "", "")

    def test_lead_capture_stores_details_and_flips_status(self):
        tok = site_chat.new_session_token()
        cid = site_chat.capture_lead(1, tok, "Ram Sharma", "ram@example.com", "9800000000", "Sharma Traders")
        conv = db.query("SELECT * FROM chatbot_conversation WHERE id = ?", (cid,), one=True)
        self.assertEqual(conv["name"], "Ram Sharma")
        self.assertEqual(conv["email"], "ram@example.com")
        self.assertEqual(conv["status"], "needs_follow_up")

    def test_site_context_includes_published_services_only(self):
        db.execute("INSERT INTO firm (id, name) VALUES (1, 'Test Firm') ON CONFLICT(id) DO NOTHING")
        db.execute("INSERT INTO service (firm_id, slug, name, summary, is_published) VALUES (1, 'a', 'Published Service', 'yes', 1)")
        db.execute("INSERT INTO service (firm_id, slug, name, summary, is_published) VALUES (1, 'b', 'Draft Service', 'no', 0)")
        ctx = site_chat._build_site_context(1)
        self.assertIn("Published Service", ctx)
        self.assertNotIn("Draft Service", ctx)

    def test_message_cap_enforced_per_conversation(self):
        tok = site_chat.new_session_token()
        conv = site_chat._get_or_create_conversation(1, tok)
        for _ in range(site_chat._MAX_MESSAGES_PER_CONVERSATION):
            db.execute("INSERT INTO chatbot_message (conversation_id, role, content) VALUES (?, 'user', 'x')",
                      (conv["id"],))
        with self.assertRaises(site_chat.ChatError):
            site_chat.reply(1, tok, "one more message")


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