#!/usr/bin/env python3
import hashlib,json,re,sys
from collections import defaultdict
from difflib import SequenceMatcher
from pathlib import Path

def norm(s):return re.sub(r'\s+',' ',re.sub(r'[^\w\s]',' ',s.lower(),flags=re.UNICODE)).strip()
def fail(m):print(json.dumps({'status':'failed','error':m},ensure_ascii=False));raise SystemExit(1)
def main(raw):
 p=Path(raw);d=json.loads(p.read_text());paras=[]
 for part in d.get('parts',[]):
  for idx,x in enumerate(part.get('paragraphs',[]),1):paras.append((part['part'],idx,x))
 if len(paras)!=144:fail(f'expected 144 paragraphs, got {len(paras)}')
 sentences=[]
 for part,pi,text in paras:
  for si,s in enumerate(re.split(r'(?<=[.!?…])\s+',text),1):
   n=norm(s)
   if len(n.split())>=6:sentences.append((part,pi,si,s,n))
 exact=defaultdict(list)
 for x in sentences:exact[x[4]].append(x[:4])
 exact_hits=[{'text':k,'locations':v} for k,v in exact.items() if len(v)>1]
 para_map=defaultdict(list)
 for part,pi,text in paras:para_map[norm(text)].append((part,pi))
 para_hits=[{'text':k,'locations':v} for k,v in para_map.items() if len(v)>1]
 ngrams={12:defaultdict(list),16:defaultdict(list)}
 for part,pi,text in paras:
  toks=norm(text).split()
  for n in (12,16):
   for i in range(len(toks)-n+1):ngrams[n][' '.join(toks[i:i+n])].append((part,pi,i))
 ng_hits={str(n):[{'ngram':g,'locations':locs} for g,locs in mp.items() if len({(x[0],x[1]) for x in locs})>1] for n,mp in ngrams.items()}
 near=[]
 for i in range(len(paras)):
  a=norm(paras[i][2])
  for j in range(i+1,len(paras)):
   b=norm(paras[j][2])
   if min(len(a),len(b))<180:continue
   r=SequenceMatcher(None,a,b).ratio()
   if r>=0.90:near.append({'a':paras[i][:2],'b':paras[j][:2],'ratio':round(r,4)})
 violations=len(exact_hits)+len(para_hits)+sum(len(v) for v in ng_hits.values())+len(near)
 out={'status':'passed' if violations==0 else 'failed','story_sha256':hashlib.sha256(p.read_bytes()).hexdigest(),'paragraphs':len(paras),'sentences':len(sentences),'exact_sentence_duplicates':exact_hits,'exact_paragraph_duplicates':para_hits,'repeated_ngrams':ng_hits,'near_duplicates':near,'violation_count':violations}
 print(json.dumps(out,ensure_ascii=False,indent=2))
 if violations:raise SystemExit(1)
if __name__=='__main__':
 if len(sys.argv)!=2:fail('usage auditor STORY.json')
 main(sys.argv[1])
