#!/usr/bin/env python3
"""Bounded readonly source inventory. Not a PHP whole-program/dataflow proof."""
import hashlib
import json
import re
import subprocess
from pathlib import Path

R=Path(__file__).resolve().parent
WS=R.parents[4]
STORE=WS/'zhctproject/store'
AI=WS/'zhctproject/ai_api'
targets={
 'application/p/controller/Nutrition.php':('Nutrition',['model']),
 'application/m/controller/Nutrition.php':('Nutrition',['model']),
 'application/p/logic/Nutrition.php':('Nutrition',['model']),
 'application/m/logic/Nutrition.php':('Nutrition',['model']),
 'application/p/controller/NutritionTemplate.php':('NutritionTemplate',['model']),
 'application/p/logic/NutritionTemplate.php':('NutritionTemplate',['model']),
 'application/p/controller/NutritionCategory.php':('Nutrition',['NutritionModel']),
 'application/m/controller/NutritionCategory.php':('Nutrition',['NutritionModel']),
 'application/p/controller/NutritionSet.php':('Nutrition',['NutritionModel']),
 'application/p/logic/NutritionCategory.php':('Nutrition',['NutritionModel']),
 'application/p/logic/NutritionScheme.php':('Nutrition',['nutritionModel']),
 'application/p/logic/Api.php':('Nutrition',['nutritionModel']),
 'application/p/logic/NutritionStation.php':('Nutrition',['modelNutrition']),
}
mutators={'add','addAll','modify','modifyById','modifyByWhere','insert','insertGetId','insertAll','save','saveAll','update','delMore','del','delById','delByWhere','delete','setInc','setDec','setField'}


def location(text,start):
    line=text.count('\n',0,start)+1
    f=list(re.finditer(r'\bfunction\s+([A-Za-z_][A-Za-z_0-9]*)\s*\(',text[:start]))
    return {'line':line,'function':f[-1].group(1) if f else '(class scope)'}


def run():
    files=list((STORE/'application').rglob('*.php'))
    raw=[];models=[];hashes={};imports=[]
    for path in files:
        text=path.read_text(errors='strict');rel=path.relative_to(STORE).as_posix()
        if rel in targets:
            table,vars=targets[rel]
            hashes[rel]=hashlib.sha256(path.read_bytes()).hexdigest()
            for var in vars:
                for match in re.finditer(r'\$this->'+re.escape(var)+r'\s*->[^;]+;',text,re.S):
                    snippet=match.group();calls=re.findall(r'->([A-Za-z_][A-Za-z_0-9]*)\s*\(',snippet)
                    models.append({'path':rel,'target':table,'property':var,**location(text,match.start()),'calls':calls,'potential_mutation':any(c in mutators for c in calls),'statement':snippet})
        for match in re.finditer(r'(?i)\bDb::name\(\s*([\"\x27])(nutrition|nutrition_template)\1\s*\)[^;]+;',text,re.S):
            prefix=text[text.rfind('\n',0,match.start())+1:match.start()]
            raw.append({'path':rel,'target':match.group(2),**location(text,match.start()),'line_comment':prefix.lstrip().startswith('//'),
                        'calls':re.findall(r'->([A-Za-z_][A-Za-z_0-9]*)\s*\(',match.group()),'statement':match.group()})
            hashes[rel]=hashlib.sha256(path.read_bytes()).hexdigest()
        for match in re.finditer(r'(?i)(?:use\s+app\\model\\(?:Nutrition|NutritionTemplate)\b[^;]*;|new\s+\\app\\model\\(?:Nutrition|NutritionTemplate)\b[^;]*;)',text):
            imports.append({'path':rel,**location(text,match.start()),'statement':match.group()})
            assert rel in targets,('unreviewed model alias',rel,match.group())
    raw_mutations=[x for x in raw if not x['line_comment'] and any(c in mutators for c in x['calls'])]
    extra=['application/model/BaseModel.php','application/model/Nutrition.php','application/model/NutritionTemplate.php',
           'application/model/NutritionTemplateExt.php','application/validate/Nutrition.php','application/validate/NutritionTemplate.php',
           'thinkphp/library/think/Model.php','thinkphp/library/think/db/Query.php','thinkphp/library/think/db/Builder.php','thinkphp/library/think/db/Connection.php']
    for rel in extra:hashes[rel]=hashlib.sha256((STORE/rel).read_bytes()).hexdigest()
    ai_excluded=['app/store/model/nutrition/Nutrition.php','app/common/model/nurtition/Nurtition.php']
    return {'status':'READONLY_STATICALLY_NAMED_RUNTIME_SOURCE_AUDIT','workspace':str(WS),'store_HEAD':subprocess.check_output(['git','-C',str(STORE),'rev-parse','HEAD'],text=True).strip(),
            'searched_store_application_php_files':len(files),'reviewed_target_alias_files':list(targets),
            'target_model_alias_discovery':imports,'target_model_calls':models,'raw_Db_name_exact_target_calls':raw,
            'raw_Db_name_actual_mutations':raw_mutations,'target_model_potential_mutations':[x for x in models if x['potential_mutation']],
            'source_sha256':hashes,'excluded_AI_same_name':{rel:hashlib.sha256((AI/rel).read_bytes()).hexdigest() for rel in ai_excluded},
            'limitations':['Static named callsite audit, not dynamic execution/callgraph proof; external writers/raw SQL outside searched runtime source not proven absent.',
                           'Imports/fully-qualified constructors are mechanically checked against reviewed alias ownership; commented Db sites are listed separately.',
                           'Db::name exact nutrition sites found are reads/counts/columns, not writes in this snapshot; a future raw writer would bypass model-only mapping.']}


if __name__=='__main__':print(json.dumps(run(),ensure_ascii=False,indent=2))
