"""Deterministic source evidence and bounded checks for seeded analyst-answer examples."""
from __future__ import annotations
import csv
import hashlib
import io
import json
import re
from datetime import datetime
from pathlib import Path

ROOT=Path(__file__).resolve().parent
SOURCE=ROOT.parent/'commerce-sql'/'orders.csv'
FIELDS=['order_id','customer_id','ordered_at','status','order_total_paise','discount_paise']
ANSWER_FIELDS={'metric_id','period_start','period_end','timezone','currency','unit','value',
               'eligible_order_count','evidence_order_ids','claims'}


def contract():return json.loads((ROOT/'contract.json').read_text(encoding='utf-8'))


def calculate(source=SOURCE):
    rules=contract();raw=Path(source).read_bytes()
    reader=csv.DictReader(io.StringIO(raw.decode('utf-8'),newline=''),strict=True)
    if reader.fieldnames!=FIELDS:raise ValueError('source_header_mismatch')
    rows=list(reader);seen=set();eligible=[]
    start,end=datetime.fromisoformat(rules['period_start']),datetime.fromisoformat(rules['period_end'])
    for row in rows:
        if set(row)!=set(FIELDS) or any(v is None for v in row.values()):raise ValueError('source_row_shape')
        if not row['order_id'] or row['order_id'] in seen:raise ValueError('duplicate_or_missing_order_id')
        seen.add(row['order_id'])
        when=datetime.fromisoformat(row['ordered_at'])
        if when.tzinfo is not None:raise ValueError('expected_declared_local_timestamp')
        if row['status'] not in {'completed','cancelled','pending'}:raise ValueError('invalid_status')
        if not re.fullmatch(r'0|[1-9][0-9]*',row['order_total_paise']):raise ValueError('invalid_amount')
        if start<=when<end and row['status']==rules['eligible_status']:
            eligible.append({'order_id':row['order_id'],'amount_paise':int(row['order_total_paise'])})
    eligible.sort(key=lambda row:row['order_id'])
    return {'metric_id':rules['metric_id'],'period_start':rules['period_start'],'period_end':rules['period_end'],
            'timezone':rules['timezone'],'currency':rules['currency'],'unit':rules['unit'],
            'value':sum(row['amount_paise'] for row in eligible),'eligible_order_count':len(eligible),
            'evidence_order_ids':[row['order_id'] for row in eligible],'source_rows':eligible,
            'source_sha256':hashlib.sha256(raw).hexdigest(),
            'contract_sha256':hashlib.sha256((ROOT/'contract.json').read_bytes()).hexdigest(),
            'limitations':rules['limitations']}


def check_candidate(answer,evidence=None):
    evidence=calculate() if evidence is None else evidence
    issues=[]
    if not isinstance(answer,dict) or set(answer)!=ANSWER_FIELDS:
        return {'structured_checks_passed':False,'issues':['answer_schema_mismatch'],'human_review_required':True}
    for field in ('metric_id','period_start','period_end','timezone','currency','unit'):
        if answer[field]!=evidence[field]:issues.append(field+'_mismatch')
    for field in ('value','eligible_order_count'):
        if type(answer[field]) is not int or answer[field]!=evidence[field]:issues.append(field+'_mismatch')
    ids=answer['evidence_order_ids']
    if not isinstance(ids,list) or not all(isinstance(v,str) for v in ids):issues.append('invalid_evidence_ids')
    elif len(ids)!=len(set(ids)) or sorted(ids)!=evidence['evidence_order_ids']:issues.append('evidence_ids_mismatch')
    claims=answer['claims']
    if not isinstance(claims,list) or not claims:issues.append('missing_claims')
    else:
        for claim in claims:
            if not isinstance(claim,dict) or set(claim)!={'kind','text','evidence_ids'}:
                issues.append('claim_schema_mismatch');continue
            if claim['kind']!='descriptive':issues.append('unsupported_claim_kind')
            if not isinstance(claim['text'],str) or not claim['text'].strip():issues.append('missing_claim_text')
            cited=claim['evidence_ids']
            if not isinstance(cited,list) or not cited or not all(isinstance(v,str) and v in evidence['evidence_order_ids'] for v in cited):
                issues.append('invalid_claim_evidence')
    return {'structured_checks_passed':not issues,'issues':issues,'human_review_required':True,
            'limits':'Checks structured facts and declared claim kind; does not establish semantic support for arbitrary claim text or human approval.'}


def seeded_cases():
    from copy import deepcopy
    evidence=calculate()
    base={field:evidence[field] for field in sorted(ANSWER_FIELDS) if field!='claims'}
    base['claims']=[{'kind':'descriptive','text':'The eligible completed-order amount is 104000 paise across 8 orders.',
                     'evidence_ids':list(evidence['evidence_order_ids'])}]
    cases=[{'case_id':'correct_structured_answer','expected_pass':True,'answer':deepcopy(base)}]
    for case_id,changes in [
        ('header_join_fanout',{'value':171000}),('customer_inner_join_loss',{'value':95000,'eligible_order_count':7}),
        ('distinct_amount_error',{'value':82000}),('wrong_unit',{'unit':'INR'}),
        ('wrong_period',{'period_end':'2026-02-02T00:00:00'}),('wrong_order_count',{'eligible_order_count':12}),
        ('missing_order_evidence',{'evidence_order_ids':evidence['evidence_order_ids'][:-1]}),
        ('numeric_string',{'value':'104000'}),
    ]:
        answer=deepcopy(base);answer.update(changes)
        cases.append({'case_id':case_id,'expected_pass':False,'answer':answer})
    causal=deepcopy(base);causal['claims'][0]={'kind':'causal','text':'The campaign caused the completed-order amount.',
                                          'evidence_ids':list(evidence['evidence_order_ids'])}
    cases.append({'case_id':'declared_unsupported_causality','expected_pass':False,'answer':causal})
    disguised=deepcopy(base);disguised['claims'][0]['text']='The campaign caused the completed-order amount.'
    cases.append({'case_id':'semantic_blind_spot_requires_human_review','expected_pass':True,
                  'note':'Deliberately mislabeled prose passes these narrow structural checks; it must fail semantic human review.',
                  'answer':disguised})
    return cases


def build():
    evidence=calculate();cases=seeded_cases()
    assert evidence['value']==104000 and evidence['eligible_order_count']==8
    assert evidence['evidence_order_ids']==['O1001','O1002','O1003','O1005','O1006','O1007','O1008','O1009']
    results=[]
    for case in cases:
        result=check_candidate(case['answer'],evidence)
        assert result['structured_checks_passed']==case['expected_pass'],case['case_id']
        assert result['human_review_required'] is True
        results.append({'case_id':case['case_id'],**result,'expected_structured_result_matched':True})
    for name,value in [('reference-evidence.json',evidence),('seeded-candidates.json',cases),
                       ('verification-results.json',{'synthetic':True,'live_model_calls':0,'seeded_cases':len(cases),
                        'reference_amount_paise':104000,'reference_orders':8,'results':results,
                        'limits':'Authored candidates test the checker, not an AI model. One intentional semantic blind spot demonstrates why structural checks are insufficient.'})]:
        (ROOT/name).write_text(json.dumps(value,indent=2)+'\n',encoding='utf-8')
    print(json.dumps({'reference_amount_paise':104000,'reference_orders':8,'seeded_cases_checked':len(cases),
                      'live_model_calls':0,'semantic_review_required':True}))


if __name__=='__main__':build()
