import asyncio
import importlib.util
import json
from pathlib import Path
import sqlite3
import tempfile
import unittest
from aiohttp.test_utils import TestClient,TestServer
from aiohttp import WSMsgType
from consigliere_server import create_app,snapshot,diagnostics

class Tests(unittest.IsolatedAsyncioTestCase):
    async def asyncSetUp(self):
        self.temp=tempfile.TemporaryDirectory();self.root=Path(self.temp.name).resolve();self.token='test-token-'+'x'*48
        c=sqlite3.connect(self.root/'qzotic_pure_sandbox.db');c.execute('CREATE TABLE enterprise_portfolio(project_name TEXT,industry_vertical TEXT,lifecycle_state TEXT,supervisor_deadline TEXT,allocated_budget_cents INTEGER)');c.execute("INSERT INTO enterprise_portfolio VALUES('Real unit','CREATIVE','ACTIVE','2026-12-01T00:00:00Z',900000000000000000)");c.execute("INSERT INTO enterprise_portfolio VALUES('TEST_NODE','TEST','ACTIVE','2026-12-01T00:00:00Z',100)");c.commit();c.close()
        c=sqlite3.connect(self.root/'luxzia_research.db');c.execute('CREATE TABLE lucxia_knowledge_history(topic_name TEXT,verbatim_focus_text TEXT)');c.execute("INSERT INTO lucxia_knowledge_history VALUES('Private topic','DO NOT EXPOSE SECRET NOTES')");c.commit();c.close()
        self.origin='https://dashboard.example.test';self.client=TestClient(TestServer(create_app(self.root,self.token,[self.origin])));await self.client.start_server()
    async def asyncTearDown(self):await self.client.close();self.temp.cleanup()
    def headers(self):return {'Authorization':'Bearer '+self.token,'Origin':self.origin}
    async def test_unauthorized_snapshot(self):self.assertEqual((await self.client.get('/api/snapshot')).status,401)
    async def test_bad_origin(self):self.assertEqual((await self.client.get('/api/snapshot',headers={'Authorization':'Bearer '+self.token,'Origin':'https://evil.test'})).status,403)
    async def test_authenticated_snapshot_decimal_strings(self):
        r=await self.client.get('/api/snapshot',headers=self.headers());self.assertEqual(r.status,200);data=await r.json();self.assertEqual(data['portfolio']['total_budget_cents'],'900000000000000000');self.assertEqual(len(data['portfolio']['units']),1)
        self.assertNotIn('DO NOT EXPOSE',json.dumps(data));self.assertEqual(data['research']['topic_count'],1);self.assertEqual(r.headers['Cache-Control'],'no-store')
    async def test_snapshot_no_modification(self):
        before=(self.root/'qzotic_pure_sandbox.db').read_bytes();await self.client.get('/api/snapshot',headers=self.headers());self.assertEqual(before,(self.root/'qzotic_pure_sandbox.db').read_bytes())
    async def test_websocket_auth_and_readonly_commands(self):
        ws=await self.client.ws_connect('/api/stream',headers={'Origin':self.origin});await ws.send_json({'type':'authenticate','token':self.token});auth=await ws.receive_json();self.assertEqual(auth['type'],'authenticated');data=await ws.receive_json();self.assertEqual(data['type'],'snapshot');await ws.send_json({'action_intent':'trade'});reply=await ws.receive_json();self.assertEqual(reply['type'],'command_rejected');await ws.close()
    async def test_websocket_invalid_auth(self):
        ws=await self.client.ws_connect('/api/stream',headers={'Origin':self.origin});await ws.send_json({'type':'authenticate','token':'wrong'});msg=await ws.receive();self.assertEqual(msg.type,WSMsgType.CLOSE)
    async def test_diagnostic_post_and_poll(self):
        r=await self.client.post('/api/diagnostics',json={},headers=self.headers());self.assertEqual(r.status,202);job=await r.json()
        for _ in range(20):
            result=await (await self.client.get('/api/diagnostics/'+job['job_id'],headers=self.headers())).json()
            if result['status']=='COMPLETED':break
            await asyncio.sleep(.01)
        self.assertEqual(result['status'],'COMPLETED');self.assertEqual(result['result']['database'],'PASS');self.assertEqual(result['result']['live_actions'],'DISABLED')
    async def test_diagnostics_requires_auth(self):self.assertEqual((await self.client.post('/api/diagnostics',json={})).status,401)
    async def test_unknown_job(self):self.assertEqual((await self.client.get('/api/diagnostics/missing',headers=self.headers())).status,404)
    async def test_disallowed_route(self):self.assertEqual((await self.client.get('/credentials.env')).status,404)
    async def test_database_missing(self):
        p=self.root/'qzotic_pure_sandbox.db';p.unlink();data=await (await self.client.get('/api/snapshot',headers=self.headers())).json();self.assertEqual(data['portfolio']['status'],'UNAVAILABLE_OR_SCHEMA_MISMATCH');self.assertFalse(p.exists())
    async def test_research_alias_conflict(self):
        (self.root/'lucxia_research.db').touch();self.assertEqual(snapshot(self.root)['research']['status'],'UNAVAILABLE')
    async def test_cors_preflight(self):
        r=await self.client.options('/api/diagnostics',headers={'Origin':self.origin});self.assertEqual(r.status,204);self.assertEqual(r.headers['Access-Control-Allow-Origin'],self.origin)

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