import copy,json,sys,unittest
from odexa_ref import crypto
from profiles import free_contracts as f, paid_contracts as p, storage_contracts as c
from test_free_contracts import fixtures as free_fixture, NOW, later
from test_paid_contracts import fixtures as paid_fixture


def fixtures(paid=False):
    values,context=(paid_fixture if paid else free_fixture)()
    for name in ('offer_request','offer','acceptance','receipt'):values[name]['reporting_profile']=c.PROFILE
    ob=crypto.json_bytes(values['offer']);values['acceptance']['offer_digest']=crypto.digest(ob)
    values['receipt'].update(offer_digest=crypto.digest(ob),acceptance_digest=crypto.digest(crypto.json_bytes(values['acceptance'])))
    return values,context


def schema_cases():
    cases=[]
    for paid in (False,True):
        v,_=fixtures(paid); prefix='paid_' if paid else 'free_'
        for name in ('offer_request','offer','acceptance','receipt'):
            cases.append(dict(name=prefix+name,definition=prefix+name,value=v[name],structural_valid=True,semantic_valid=True))
            for missing in (True,False):
                x=copy.deepcopy(v[name])
                if missing:x.pop('reporting_profile')
                else:x['reporting_profile']='storage_sessions_v2'
                cases.append(dict(name=prefix+name+('_missing' if missing else '_unknown'),definition=prefix+name,value=x,structural_valid=False,semantic_valid=False))
    return dict(schema='storage-agreement',cases=cases)


class SelectedStorageContractsTests(unittest.TestCase):
    def test_exact_free_and_paid_selection_chain(self):
        for paid in (False,True):
            v,ctx=fixtures(paid);api=c.PAID if paid else c.FREE
            ob,ab=map(crypto.json_bytes,(v['offer'],v['acceptance']))
            api.validate_offer(v['offer'],v['offer_request']);api.validate_acceptance(v['acceptance'],ob,NOW)
            api.validate_receipt(v['receipt'],ob,ab);api.validate_status(v['status'],v['receipt'])
            api.validate_token_response(v['token_response'],v['receipt'],NOW)
            api.validate_introspection_response(v['introspection_response'],v['introspection_request'],v['receipt'],v['token_response'])
            if paid:api.bind_quote(v['offer'],ctx)
            else:api.validate_context_bundle(v['offer'],policy_bytes=ctx[0],authority_bytes=ctx[1],terms_bytes=ctx[2],now=NOW)

    def test_selection_is_closed_and_never_inferred(self):
        for paid in (False,True):
            values,_=fixtures(paid);api=c.PAID if paid else c.FREE;old=p if paid else f
            for name in ('offer_request','offer','acceptance','receipt'):
                with self.assertRaises(ValueError):getattr(old,'validate_'+name)(values[name])
                for profile in (None,'unknown',False):
                    v=copy.deepcopy(values[name]);v['reporting_profile']=profile
                    with self.assertRaises(ValueError):getattr(api,'validate_'+name)(v)

    def test_exact_wire_digest_not_projected_base_digest(self):
        values,_=fixtures();ob=crypto.json_bytes(values['offer']);ab=crypto.json_bytes(values['acceptance'])
        v=copy.deepcopy(values['acceptance']);v['offer_digest']=crypto.digest(crypto.json_bytes(c.base(values['offer'])))
        with self.assertRaises(ValueError):c.FREE.validate_acceptance(v,ob)
        receipt=copy.deepcopy(values['receipt']);receipt['acceptance_digest']=crypto.digest(crypto.json_bytes(c.base(values['acceptance'])))
        with self.assertRaises(ValueError):c.FREE.validate_receipt(receipt,ob,ab)

    def test_selected_receipt_does_not_extend_or_rebind(self):
        for paid in (False,True):
            v,_=fixtures(paid);api=c.PAID if paid else c.FREE;ob=crypto.json_bytes(v['offer']);ab=crypto.json_bytes(v['acceptance'])
            for field,value in [('access_expires_at',later(301)),('recorded_at',later(60)),('reporting_profile','none')]:
                r=copy.deepcopy(v['receipt']);r[field]=value
                with self.assertRaises(ValueError):api.validate_receipt(r,ob,ab)
            a=copy.deepcopy(v['acceptance']);a['nonce']=crypto.b64u(b'z'*32)
            with self.assertRaises(ValueError):api.validate_acceptance(a,ob)

    def test_terms_are_pinned_to_one_collector_and_accepted_use_expiry(self):
        v,_=fixtures();endpoint='https://provider.example/tenant-a/api/events'
        v['offer']['obligations'].append(dict(type='report_usage',endpoint=endpoint,deadline_seconds=60))
        v['offer']['request']['supported_obligations'].append('report_usage')
        t=c.accepted_terms(v['offer'],v['receipt'],collector_url=endpoint)
        self.assertEqual(t['use_expires_at'],v['receipt']['use_expires_at']);self.assertEqual(t['report_deadline_seconds'],60)
        with self.assertRaises(ValueError):c.accepted_terms(v['offer'],v['receipt'],collector_url=endpoint+'/other')


if __name__=='__main__':
    if '--cases' in sys.argv:print(json.dumps(schema_cases(),indent=2))
    else:unittest.main()
