"""Independent Node storage execution against real direct/delegated TLS services."""
import json
import os
from pathlib import Path
import subprocess
import tempfile
import unittest

from odexa_ref import crypto
from profiles.storage_contracts import PROFILE
import test_free_gateway as gateway_fixture
import test_free_service as fixture
from test_storage_service import configure
from test_storage_evidence import enable_export, export_material
from profiles import storage_evidence


class StorageRuntimeTests(unittest.TestCase):
    @classmethod
    def setUpClass(cls): fixture.FreeServiceTests.setUpClass()

    @classmethod
    def tearDownClass(cls): fixture.FreeServiceTests.tearDownClass()

    def run_exchange(self, external):
        f = fixture.FreeServiceTests(); f.setUp()
        try:
            if not external:
                f.temp.cleanup(); f.temp = tempfile.TemporaryDirectory(); f.path = Path(f.temp.name)
                f.make_service(external=False)
            enable_export(f); configure(f)
            gateway = gateway_fixture.FreeGatewayTests(); gateway.f = f; gateway.configure_gateway()
            node = os.environ.get('ODEXA_NODE', 'node')
            private = dict(crypto.public_jwk(f.agent_key), d=crypto.b64u(f.agent_key.private_numbers().private_value.to_bytes(32, 'big')))
            config = dict(publisherOrigin=f.origin, serviceId=f.binding['service_id'], baseUrl=f.base,
                delegationId=f.binding['delegation_id'], caFile=str(f.tls_path/'ca.pem'),
                agent=dict(clientId='agent', principalId=f.principal, password='a'*43,
                    keyId=f.clients['agent']['key_id'], reporterId=f.clients['agent']['reporter_id'], privateJwk=private),
                request=f.request, accessSeconds=120, useSeconds=240,
                expectedTermsDigest=crypto.digest(f.service.terms_bytes), includeResource=True,
                reportingProfile=PROFILE, exerciseStorage=True)
            config_path = f.path/'node-client-private.json'
            config_path.write_bytes(crypto.json_bytes(config)); config_path.chmod(0o600)
            name = 'storage-delegated' if external else 'storage-direct'
            out = Path(os.environ.get('ODEXA_STORAGE_REPORT_DIR', str(f.path/'reports')))/name
            result = subprocess.run([node, 'verification/free-client.mjs', '--config', str(config_path), '--outdir', str(out)],
                capture_output=True, text=True, timeout=45)
            report = json.loads((out/'free-client-report.json').read_text())
            self.assertEqual(result.returncode, 0, report)
            self.assertEqual((report['passed'], report['failed']), (14, 0), report)
            self.assertEqual(report['storage'], dict(managed_copies=3, signed_reports=9,
                all_closed=True, exact_deadline_inheritance=True, reopened=True))
            records = gateway.gateway.records()
            self.assertEqual(len(records), 1)
            self.assertEqual((records[0]['state'], records[0]['bytes_written']), ('sent', len(gateway.asset)))
            with f.store.connect() as db:
                self.assertEqual(db.execute('SELECT count(*) FROM records').fetchone()[0], 10)
                agreement = db.execute('SELECT id,state FROM agreements').fetchone()
            self.assertEqual(agreement['state'], 'revoked')
            metrics = f.service.storage_metrics(agreement['id'], as_of=f.now())
            self.assertEqual(metrics['completed_action_claims'], 4)
            self.assertEqual(metrics['sessions_started'], 3)
            self.assertEqual(metrics['unresolved_sessions'], 0, metrics)
            body,trust,_=export_material(f,agreement['id'])
            imported=storage_evidence.StorageEvidenceArchive(f.path/'archive'/'evidence.sqlite3').import_bundle(body,trust=trust,now=f.now)
            self.assertEqual(imported['storage_metrics']['sessions_closed'],3)
            self.assertEqual(imported['storage_metrics']['completed_action_claims'],4)
            self.assertEqual(imported['storage_metrics']['unresolved_sessions'],0)
            self.assertFalse(imported['reactivates_access'])
            for method, path, headers, body in f.origin_server.requests:
                if path in {'/odexa.json', '/odexa-service.json'}:
                    self.assertNotIn('Authorization', headers)
            if not external: self.assertFalse(f.provider_server.requests)
            # Private signing material is never retained in the managed journal.
            journal = (f.path/'managed-storage'/'.odexa-storage.json').read_bytes()
            self.assertNotIn(private['d'].encode(), journal)
        finally: f.tearDown()

    def test_provider_independent_agent_storage_and_signed_reporting(self): self.run_exchange(False)

    def test_two_origin_agent_storage_and_signed_reporting(self): self.run_exchange(True)


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