import json
from pathlib import Path
import sqlite3
import tempfile
import unittest
import uuid
from unittest.mock import patch
from luxzia_core import LuxziaResearchSystem as LucxiaResearchSystem, timestamp


class Tests(unittest.TestCase):
    def setUp(self):
        self.tmp=tempfile.TemporaryDirectory();self.root=Path(self.tmp.name).resolve();self.path=self.root/'lucxia_research.db';self.system=LucxiaResearchSystem(self.path)
    def tearDown(self):
        self.system.close();self.tmp.cleanup()
    def test_separate_database_and_empty_start(self):
        self.assertFalse((self.root/'qzotic_pure_sandbox.db').exists());self.assertEqual(self.system.explore_timeline_horizon()['scenarios'],[])
        with self.assertRaises(ValueError):LucxiaResearchSystem(self.root/'qzotic_pure_sandbox.db')
    def test_journal_revisions_preserve_text(self):
        self.system.record_knowledge_revision('Research','first','source','feedback');self.system.record_knowledge_revision('Research','second')
        self.assertEqual([r[0] for r in self.system.conn.execute('SELECT verbatim_focus_text FROM lucxia_knowledge_history ORDER BY history_id')],['first','second'])
    def test_history_append_only(self):
        self.system.record_knowledge_revision('Research','notes')
        for sql in ('DELETE FROM lucxia_knowledge_history',"UPDATE lucxia_knowledge_history SET topic_name='changed'"):
            with self.assertRaises(sqlite3.IntegrityError):self.system.conn.execute(sql)
    def test_no_implicit_sample_records(self):
        self.assertEqual(self.system.conn.execute('SELECT COUNT(*) FROM lucxia_knowledge_history').fetchone()[0],0)
    def test_fictional_seed_idempotent(self):
        self.system.seed_fictional_scenarios();self.system.seed_fictional_scenarios()
        self.assertEqual(self.system.conn.execute('SELECT COUNT(*) FROM lucxia_fictional_scenarios').fetchone()[0],5)
        row=self.system.conn.execute('SELECT * FROM lucxia_fictional_scenarios LIMIT 1').fetchone()
        self.assertEqual(row['simulated_delivery_deadline'],'2026-10-29T23:59:59+00:00')
    def test_exploration_does_not_read_clock(self):
        self.system.seed_fictional_scenarios()
        with patch('luxzia_core.utc_now',side_effect=AssertionError('clock read')):self.assertEqual(len(self.system.explore_timeline_horizon()['scenarios']),5)
    def test_reminder_before_boundary(self):
        self.system.schedule_reminder('TEST','2026-11-01T23:59:59Z','reminder')
        self.assertEqual(self.system.check_and_queue_scheduled_reminders('2026-10-29T23:59:58Z'),[])
    def test_exact_boundary_queues_real_outbox_once(self):
        self.system.schedule_reminder('TEST','2026-11-01T23:59:59Z','reminder')
        self.assertEqual(self.system.check_and_queue_scheduled_reminders('2026-10-29T23:59:59Z')[0]['state'],'QUEUED_TO_OUTBOX')
        self.assertEqual(self.system.check_and_queue_scheduled_reminders('2026-10-30T00:00:00Z'),[])
        self.assertEqual(self.system.conn.execute('SELECT COUNT(*) FROM lucxia_notification_outbox').fetchone()[0],1)
    def test_late_run_marks_missed_without_delivery_claim(self):
        self.system.schedule_reminder('TEST','2026-11-01T23:59:59Z','reminder')
        self.assertEqual(self.system.check_and_queue_scheduled_reminders('2026-11-02T00:00:00Z')[0]['state'],'MISSED_LATE_RUN')
        self.assertEqual(self.system.conn.execute('SELECT COUNT(*) FROM lucxia_notification_outbox').fetchone()[0],0)
    def test_timezone_rejection_and_offset_normalization(self):
        with self.assertRaises(ValueError):self.system.schedule_reminder('TEST','2026-11-01','reminder')
        self.assertEqual(timestamp('2026-11-01T18:59:59-05:00').isoformat(),'2026-11-01T23:59:59+00:00')
    def test_idempotent_request_and_conflict(self):
        uid=str(uuid.uuid4());a=self.system.schedule_reminder('TEST','2026-11-01T23:59:59Z','reminder',uid)
        self.assertEqual(self.system.schedule_reminder('TEST','2026-11-01T23:59:59Z','reminder',uid),a)
        with self.assertRaises(ValueError):self.system.schedule_reminder('TEST','2026-11-01T23:59:59Z','changed',uid)
    def test_pending_suppression(self):
        rid=self.system.schedule_reminder('TEST','2026-11-01T23:59:59Z','reminder');self.system.suppress_reminder(rid)
        self.assertEqual(self.system.check_and_queue_scheduled_reminders('2026-10-31T00:00:00Z'),[])
    def test_atomic_queue_rollback(self):
        self.system.schedule_reminder('TEST','2026-11-01T23:59:59Z','reminder')
        self.system.conn.execute("CREATE TRIGGER reject_queue BEFORE INSERT ON lucxia_notification_outbox BEGIN SELECT RAISE(ABORT,'test rejection'); END")
        with self.assertRaises(sqlite3.IntegrityError):self.system.check_and_queue_scheduled_reminders('2026-10-31T00:00:00Z')
        self.assertEqual(self.system.conn.execute('SELECT processing_state FROM lucxia_reminder_queue').fetchone()[0],'PENDING')
    def test_verification_reference_required(self):
        with self.assertRaises(ValueError):self.system.record_event('2026-10-13T00:00:00Z','notes','EVIDENCE_REVIEWED')
        self.system.record_event('2026-10-13T00:00:00Z','notes','MANUALLY_DESCRIBED')
    def test_lock_prevents_multiple_processors(self):
        with self.assertRaises(BlockingIOError):LucxiaResearchSystem(self.path)
    def test_existing_unsupported_database_preserved(self):
        p=self.root/'legacy.db';c=sqlite3.connect(p);c.execute('CREATE TABLE legacy(value TEXT)');c.execute("INSERT INTO legacy VALUES('original')");c.commit();c.close();p.chmod(0o600)
        with self.assertRaises(ValueError):LucxiaResearchSystem(p)
        c=sqlite3.connect(p);self.assertEqual(c.execute('SELECT value FROM legacy').fetchone()[0],'original');c.close()
    def test_restart_preserves_registry(self):
        self.system.record_knowledge_revision('TEST','notes');self.system.close();self.system=LucxiaResearchSystem(self.path)
        self.assertEqual(self.system.conn.execute('SELECT COUNT(*) FROM lucxia_knowledge_history').fetchone()[0],1)


if __name__=='__main__':unittest.main(verbosity=2)
