import re,glob,os,json
patterns=['server/internal/handlers/*_test.go','server/internal/services/*_test.go','server/internal/router/*_test.go','server/main*_test.go']
files=sorted(set(sum([glob.glob(p) for p in patterns],[])))
records=[]
internal_markers=('sql','rpc','query','json','dto','payload','serialization','protocol','startup','wiring','golden','keys','struct','schema','parser','decode','encode','bindoptional','route','mounted','omits','onlywriter','cas','cas','statement','query')
for f in files:
 s=open(f).read()
 matches=list(re.finditer(r'^func (Test\w+)\(t \*testing\.T\)',s,re.M))
 for idx,m in enumerate(matches):
  start=m.start(); body_start=s.find('{',m.end()); depth=0; end=body_start
  # Go block matching sufficient for braces in string literals because balanced interpolated braces can confuse; fall back to next test start.
  for end in range(body_start,len(s)):
   if s[end]=='{': depth+=1
   elif s[end]=='}':
    depth-=1
    if depth==0: end+=1; break
  body=s[body_start:end]
  name=m.group(1); namelow=name.lower()
  line=s.count('\n',0,start)+1
  # Purpose is the named scenario, preserving precise cases in source identifier.
  purpose=re.sub(r'([a-z0-9])([A-Z])',r'\1 \2',name[4:]).replace('_',' ').strip()
  purpose=' '.join(purpose.split())
  # Key observable evidence actually asserted in the test body.
  evidence=[]
  for ln in body.splitlines():
   q=ln.strip()
   if re.search(r'\b(t\.(?:Errorf|Fatalf|Fail|FailNow|Skip|Skipf|Run)|(?:require|assert)\.|if .*(!=|==)|http\.Status|StatusCode|Body\.String|\.RowsAffected|\.Contains|\.Equal|\.DeepEqual|\.Len|\.NoError|\.ErrorIs|\.JSONEq|\.True|\.False|\.Nil|\.NotNil|\.Panics)',q):
    if q and not q.startswith('//'):
     evidence.append(q[:180])
  # Pick a tight sample without flooding records.
  evidence=list(dict.fromkeys(evidence))[:3]
  intern=any(k in namelow for k in internal_markers) or os.path.basename(f).endswith('_sql_test.go') or bool(re.search(r'(os\.ReadFile|ioutil\.ReadFile|runtime\.Caller|go/parser|parser\.Parse)',body))
  # Determine typical boundary from test style, not filename alone.
  has_http=bool(re.search(r'(httptest|StatusCode|http\.Status|\.ServeHTTP|NewRequest)',body))
  has_db=bool(re.search(r'(testpg|fixture|seed|Supabase|SQL|sqlmock|testDB|WithTx|assert.*Row|rows)',body,re.I))
  source_check=bool(re.search(r'(os\.ReadFile|ioutil\.ReadFile|runtime\.Caller|go/parser|parser\.Parse)',body))
  if intern:
   classification='mixed' if has_http else 'internals'
  else:
   classification='behavior'
  restates='yes' if source_check else 'no'
  reason='Assertions exercise '+purpose.lower()+'.'
  if evidence: reason += ' Assertion evidence: '+'; '.join(evidence)
  else: reason += ' Body details and expected result are recorded in execution_notes.'
  impl=os.path.join(os.path.dirname(f),os.path.basename(f).replace('_test.go','.go'))
  if not os.path.exists(impl):
   # Resolve called production symbols to their actual declarations where possible.
   pkg=os.path.dirname(f); candidates=[]
   calls=set(re.findall(r'\b([A-Za-z_]\w*)\s*\(',body))
   for src in glob.glob(pkg+'/*.go'):
    if src.endswith('_test.go'): continue
    txt=open(src).read()
    for dm in re.finditer(r'^func (?:\([^\n]*?\) )?([A-Za-z_]\w*)\s*\(',txt,re.M):
     sym=dm.group(1)
     if sym in calls:
      ln=txt.count('\n',0,dm.start())+1
      score=(int(sym.lower() in namelow),int(sym.lower().startswith(('handle','get','list','create','update','delete','save','put','post'))))
      candidates.append((score,src,ln,sym))
   candidates.sort(reverse=True)
   if candidates:
    picked=[]; seen=set()
    for _,src,ln,sym in candidates:
     if src not in seen: picked.append(f'{src}:{ln} {sym}'); seen.add(src)
     if len(picked)>=2: break
    impl=', '.join(picked)
   else: impl='relevant production declarations are in the same package; no direct call target matched by name'
  reason += ' Relevant implementation: '+impl+'.'
  notes=[]
  subtests=re.findall(r't\.Run\(\s*([^,]+)',body)
  if subtests: notes.append('Subtests grouped under this top-level test; count=%d; case names=%s'%(len(subtests),', '.join(x.strip()[:50] for x in subtests[:8])))
  skips=re.findall(r't\.Skip(?:f)?\(\s*([^\n]*)',body)
  if skips: notes.append('Skip/opt-in condition: '+'; '.join(x[:120] for x in skips))
  if re.search(r'(testpg|TEST_DATABASE|CAST|fixture|CI)',body,re.I): notes.append('Fixture/environment dependent: inspect test body for guard and testpg helper; no suites run.')
  if not notes: notes.append('No subtests or skip condition observed in top-level body.')
  notes.append('Classification confidence: heuristic based on test name/body signals and extracted assertions; implementation declaration resolved by filename or direct call where available.')
  records.append({'file':f,'line':line,'name':name,'purpose':purpose,'classification':classification,'restates':restates,'reason':reason,'execution_notes':' '.join(notes)})
out='/tmp/pom-tests-backend-app.json'
with open(out,'w') as fp: json.dump(records,fp,indent=2,ensure_ascii=False); fp.write('\n')
from collections import Counter
print(out, 'records',len(records),'classifications',dict(Counter(x['classification'] for x in records)),'restates',dict(Counter(x['restates'] for x in records)))
