#!/usr/bin/env python3
"""Synthetic CLI controls, authored before run. No external catalog data. MIT."""
import copy
import hashlib
import json
from pathlib import Path
import platform
import subprocess
import sys
import tempfile

ROOT = Path(__file__).resolve().parents[1]
BASE = {'item_id':'001', 'title':'Учебная кружка', 'description':'Учебный товар редакции.',
        'url':'https://example.com/item/001', 'brand':'Учебная марка', 'seller_name':'Учебный магазин',
        'image_url':'https://example.com/image/001.png', 'availability':'in_stock', 'price':'18.00 USD'}

def encoded(rows):
    return ('\n'.join(json.dumps(x,ensure_ascii=False) for x in rows)+'\n').encode()

def change(**values):
    row=copy.deepcopy(BASE);row.update(values);return row

VARIANT = change(group_id='MUG', listing_has_variations=True, variant_dict={'color':'blue','size':'350'})
SECOND = dict(VARIANT,item_id='002',variant_dict={'color':'red','size':'350'})
CASES = []
def case(name, content, code, signals=(), warnings=()):
    CASES.append((name, encoded(content) if isinstance(content,list) else content, code, set(signals), set(warnings)))
case('minimal',[BASE],0)
case('variants',[VARIANT,SECOND],0)
for stock in ['out_of_stock','pre_order','backorder','unknown']:
    case('stock-'+stock,[change(availability=stock)],0)
case('leading-zero-id',[BASE,change(item_id='1')],0)
case('unicode-line-separator',[change(description='Текст\u2028без переноса JSONL')],0)
case('http-url',[change(url='http://example.com/item')],0)
for field in BASE:
    row=copy.deepcopy(BASE);del row[field]
    case('missing-'+field,[row],1,{'required_string'})
case('empty-brand',[change(brand=' ')],1,{'required_string'})
case('placeholder-brand',[change(brand='n/a')],1,{'placeholder'})
case('numeric-id',[change(item_id=1)],1,{'required_string'})
case('availability-wrong',[change(availability='yes')],1,{'availability_enum'})
case('duplicate-item',[BASE,BASE],1,{'duplicate_item_id'})
case('relative-url',[change(url='/item/001')],1,{'url_syntax'})
case('bad-url-port',[change(url='https://example.com:bad/item')],1,{'url_syntax'})
case('url-whitespace',[change(image_url='https://example.com/a b.png')],1,{'url_syntax'})
case('variant-no-group',[change(listing_has_variations=True,variant_dict={'size':'M'})],1,{'variant_group'},warnings={'variant_dict_ignored'})
case('group-equals-item',[dict(VARIANT,group_id='001')],1,{'variant_group'},warnings={'variant_dict_ignored'})
case('empty-variant-object',[dict(VARIANT,variant_dict={})],0,warnings={'no_variant_options'})
case('variant-missing-options',[change(group_id='G',listing_has_variations=True)],1,{'variant_options'})
case('variant-list',[dict(VARIANT,variant_dict=[])],1,{'variant_options'})
case('variant-empty-value',[dict(VARIANT,variant_dict={'size':''})],1,{'variant_options'})
case('variant-placeholder',[dict(VARIANT,variant_dict={'size':'unknown'})],1,{'placeholder'})
case('variant-na',[dict(VARIANT,variant_dict={'size':'n/a'})],1,{'placeholder'})
case('url-userinfo',[change(url='https://user:sample@example.com/item')],0)
case('different-option-keys',[VARIANT,dict(SECOND,variant_dict={'size':'350'})],1,{'group_option_keys'})
case('duplicate-combination',[VARIANT,dict(SECOND,variant_dict=VARIANT['variant_dict'])],1,{'duplicate_variant_combination'})
case('boolean-string',[change(listing_has_variations='true')],1,{'boolean_type'})
case('variant-false-ignored',[dict(VARIANT,listing_has_variations=False)],0,warnings={'variant_dict_ignored'})
case('variant-omitted-ignored',[change(variant_dict={'size':'M'})],0,warnings={'variant_dict_ignored'})
case('offer-id-duplicate',[change(offer_id='A'),change(item_id='002',offer_id='A')],1,{'duplicate_offer_id'})
case('offer-id-numeric',[change(offer_id=4)],1,{'offer_id_type'})
case('optional-null',[change(offer_id=None,group_id=None,listing_has_variations=None)],0)
case('optional-unsupported',[change(gtin='bad')],0,warnings={'optional_fields_not_checked'})
case('money-limit',[change(price='NOT_A_PRICE')],0)
case('zero-price-limit',[change(price='0.00 USD')],0)
case('false-description-limit',[change(description='Выдуманное свойство, которое код не знает.')],0)
case('invalid-json',b'{broken\n',1,{'invalid_json'})
case('not-object',b'[]\n',1,{'not_object'})
case('duplicate-json-key',b'{"item_id":"001","item_id":"002"}\n',1,{'duplicate_json_key'})
case('nonfinite-json',b'{"price":NaN}\n',1,{'invalid_json'})
case('blank-middle',encoded([BASE])+b'\n'+encoded([change(item_id='002')]),1,{'blank_line'})
case('empty-file',b'',2)
case('bad-encoding',b'\xff\n',2)
case('bom',b'\xef\xbb\xbf'+encoded([BASE]),2)
case('byte-limit',b' '* (10*1024*1024+1),2)
case('line-limit',b'{}\n' *100001,2)

# New source-wide controls; expectations authored before executing 0.1.1.
for token in ['null', 'unknown', 'n/a']:
    case('group-placeholder-'+token.replace('/','-'),[dict(VARIANT,group_id=token)],1,
         {'placeholder','variant_group'},warnings={'variant_dict_ignored'})
    case('inactive-group-placeholder-'+token.replace('/','-'),[change(group_id=token)],1,{'placeholder'})
    case('offer-placeholder-'+token.replace('/','-'),[change(offer_id=token)],1,{'placeholder'})
    case('option-placeholder-'+token.replace('/','-'),[dict(VARIANT,variant_dict={'size':token})],1,{'placeholder'})
    case('ignored-option-'+token.replace('/','-'),[change(variant_dict={'size':token})],0,
         warnings={'variant_dict_ignored'})
case('optional-empty',[change(offer_id='',group_id='',listing_has_variations='')],0)
case('optional-json-null',[change(offer_id=None,group_id=None,listing_has_variations=None,variant_dict=None)],0)
case('active-group-json-null',[dict(VARIANT,group_id=None)],1,{'variant_group'},warnings={'variant_dict_ignored'})
case('active-options-json-null',[dict(VARIANT,variant_dict=None)],1,{'variant_options'})
case('active-option-json-null',[dict(VARIANT,variant_dict={'size':None})],1,{'variant_options'})
case('active-option-numeric',[dict(VARIANT,variant_dict={'size':1})],1,{'variant_options'})
case('active-option-empty-key',[dict(VARIANT,variant_dict={'':'M'})],1,{'variant_options'})
case('option-name-unknown',[dict(VARIANT,variant_dict={'unknown':'M'})],0)
case('empty-flag-options-ignored',[change(listing_has_variations='',variant_dict={'size':'unknown'})],0,
     warnings={'variant_dict_ignored'})
case('null-flag-options-ignored',[change(listing_has_variations=None,variant_dict={'size':'n/a'})],0,
     warnings={'variant_dict_ignored'})
case('boolean-string-false',[change(listing_has_variations='false')],1,{'boolean_type'})
case('local-policy-casefold',[change(offer_id=' UNKNOWN ')],1,{'placeholder'})
case('literal-null-required',[change(brand='null')],1,{'placeholder'})
case('availability-literal-null',[change(availability='null')],1,{'availability_enum'})
case('availability-na',[change(availability='n/a')],1,{'availability_enum'})
case('unsupported-placeholder-not-checked',[change(mpn='unknown')],0,warnings={'optional_fields_not_checked'})


def main():
    results=[]
    with tempfile.TemporaryDirectory() as d:
        for name,raw,code,signals,warnings in CASES:
            p=Path(d)/(name+'.jsonl');p.write_bytes(raw);before=hashlib.sha256(raw).hexdigest()
            r=subprocess.run([sys.executable,str(ROOT/'scripts/check.py'),str(p)],capture_output=True,text=True)
            try: out=json.loads(r.stdout)
            except ValueError: out={}
            found={e['code'] for e in out.get('signals',[])}
            warns={e['code'] for e in out.get('warnings',[])}
            after=hashlib.sha256(p.read_bytes()).hexdigest()
            passed=r.returncode==code and found==signals and warns==warnings and before==after and out.get('upload_allowed') is False
            results.append({'case':name,'expected_code':code,'actual_code':r.returncode,'expected_signals':sorted(signals),
                            'input_sha256':before,'input_unchanged':before==after,'pass':passed,'output':out,'stderr':r.stderr})
            if len(raw)<50000:(ROOT/'examples'/(name+'.jsonl')).write_bytes(raw)
    summary={'kind':'synthetic_packaged_cli_subset_only','environment':{'python':platform.python_version(),'os':platform.platform()},
             'origin':'All product inputs authored by Cometa; no customer data or real catalog. MIT.',
             'script_sha256':hashlib.sha256((ROOT/'scripts/check.py').read_bytes()).hexdigest(),
             'cases':len(results),'passed':sum(r['pass'] for r in results),'results':results}
    (ROOT/'tests/results.json').write_text(json.dumps(summary,ensure_ascii=False,indent=2)+'\n')
    print(json.dumps({k:summary[k] for k in ['environment','cases','passed']}));
    for r in results:
        if not r['pass']:print('FAIL',r['case'],r['actual_code'],r['output'])
    return 0 if summary['passed']==summary['cases'] else 1
if __name__=='__main__':sys.exit(main())
