from pathlib import Path
import json, copy
ROOT=Path(__file__).resolve().parents[1]
BASE=json.loads((ROOT/'fixture/baseline-system.json').read_text())

class DocumentShareFixture:
    def __init__(self, config=None):
        self.config=copy.deepcopy(config or BASE)
        self.observations=[]
    def _doc(self, doc_id): return self.config['documents'][doc_id]
    def get_document(self, actor, authenticated, doc_id='doc-1'):
        doc=self._doc(doc_id); p=self.config['policy']
        if p['document_read_requires_authentication'] and not authenticated:
            out={'allowed':False,'classification':'AUTHENTICATION_REQUIRED','actor':actor,'doc_id':doc_id}
        elif p['document_read_requires_owner_or_collaborator'] and actor not in [doc['owner'],*doc['collaborators']]:
            out={'allowed':False,'classification':'AUTHORIZATION_DENIED','actor':actor,'doc_id':doc_id}
        else:
            out={'allowed':True,'classification':'DOCUMENT_READ_ALLOWED','actor':actor,'doc_id':doc_id,'content':doc['content']}
        self.observations.append(out); return out
    def get_metadata(self, actor, authenticated, doc_id='doc-1'):
        p=self.config['policy']; doc=self._doc(doc_id)
        if p['metadata_requires_authentication'] and not authenticated:
            out={'allowed':False,'classification':'AUTHENTICATION_REQUIRED','actor':actor,'doc_id':doc_id}
        else:
            out={'allowed':True,'classification':'METADATA_READ_ALLOWED','actor':actor,'doc_id':doc_id,'metadata':doc['metadata']}
        self.observations.append(out); return out
    def boundary_summary(self):
        zones={k:v['zone'] for k,v in self.config['components'].items()}
        return {'zones':zones,'external_storage':zones['storage_adapter']!='APP_CONTROLLED','trust_boundaries':self.config['trust_boundaries']}

def missing_record_fields(row):
    return sorted(set(BASE['record_fields'])-set(row))

def run_probe(case_id):
    if case_id=='baseline_bounded_document_flow':
        f=DocumentShareFixture(); owner=f.get_document('alice',True); collab=f.get_document('bob',True); unrelated=f.get_document('mallory',True); anon_meta=f.get_metadata('anonymous',False)
        ok=owner['allowed'] and collab['allowed'] and (not unrelated['allowed']) and (not anon_meta['allowed'])
        return {'case_id':case_id,'classification':'BASELINE_BOUNDED_EVIDENCE_OBSERVED' if ok else 'UNEXPECTED','observations':f.observations,'boundary':f.boundary_summary()}
    if case_id=='unauthorized_document_read':
        f=DocumentShareFixture(); r=f.get_document('mallory',True)
        return {'case_id':case_id,'classification':'AUTHORIZATION_DENIED' if (not r['allowed'] and r['classification']=='AUTHORIZATION_DENIED') else 'UNEXPECTED','observation':r}
    if case_id=='external_storage_boundary_mutation':
        b=copy.deepcopy(BASE); b['components']['storage_adapter']['zone']='EXTERNAL_SERVICE'; b['trust_boundaries'].append({'id':'TB-APP-EXTERNAL-STORAGE','from':'application_service','to':'storage_adapter'}); f=DocumentShareFixture(b); s=f.boundary_summary()
        return {'case_id':case_id,'classification':'TRUST_BOUNDARY_CHANGED_REVIEW_REQUIRED' if s['external_storage'] and any(x['id']=='TB-APP-EXTERNAL-STORAGE' for x in s['trust_boundaries']) else 'UNEXPECTED','boundary':s,'affected_dimensions':['trust_boundary','threat_scenario','control_and_evidence','residual_risk']}
    if case_id=='unauthenticated_metadata_endpoint':
        b=copy.deepcopy(BASE); b['policy']['metadata_requires_authentication']=False; f=DocumentShareFixture(b); r=f.get_metadata('anonymous',False)
        return {'case_id':case_id,'classification':'UNAUTHENTICATED_METADATA_EXPOSURE' if r['allowed'] else 'UNEXPECTED','observation':r,'affected_dimensions':['actor_and_entry_point','trust_boundary','threat_scenario','control_and_evidence']}
    if case_id=='control_name_without_evidence_check':
        row={k:'observed' for k in BASE['record_fields']}; row['Control']='Object-level authorization'; row['Evidence Check']=''
        return {'case_id':case_id,'classification':'CONTROL_UNVERIFIABLE' if row['Control'] and not row['Evidence Check'] else 'UNEXPECTED','row':row}
    if case_id=='evidence_field_removed':
        row={k:'observed' for k in BASE['record_fields']}; row.pop('Residual Risk'); missing=missing_record_fields(row)
        return {'case_id':case_id,'classification':'EVIDENCE_INCOMPLETE' if missing==['Residual Risk'] else 'UNEXPECTED','missing':missing}
    raise KeyError(case_id)

def publisher_selftest():
    cases=json.loads((ROOT/'fixture/probe-cases.json').read_text())['cases']; expected={x['id']:x['expected_class'] for x in cases}; obs=[run_probe(x['id']) for x in cases]
    checks=[o['classification']==expected[o['case_id']] for o in obs]
    b=obs[0]; checks += [len(b['observations'])==4, b['observations'][0]['allowed'] is True, b['observations'][1]['allowed'] is True, b['observations'][2]['classification']=='AUTHORIZATION_DENIED', b['observations'][3]['classification']=='AUTHENTICATION_REQUIRED']
    checks += [run_probe('external_storage_boundary_mutation')['boundary']['external_storage'] is True, run_probe('unauthenticated_metadata_endpoint')['observation']['actor']=='anonymous', run_probe('control_name_without_evidence_check')['row']['Evidence Check']=='', run_probe('evidence_field_removed')['missing']==['Residual Risk']]
    return {'cases':obs,'checks':checks,'pass':all(checks),'runtime_scope':'PYTHON_IN_MEMORY_SYNTHETIC_DEFENSIVE_FIXTURE_ONLY','external_target_used':False,'production_runtime_claimed':False}
