"""Local SQLite/Polygon proof runner. Only creates synthetic in-memory fixtures."""
from pathlib import Path
from functools import lru_cache
import argparse,json,math,sqlite3,time
HERE=Path(__file__).resolve().parent

def number(x):
 if isinstance(x,bool) or not isinstance(x,(int,float)) or not math.isfinite(x):raise ValueError('Coordinates must be finite numbers')
 return float(x)
def cross(a,b,c):return (b[0]-a[0])*(c[1]-a[1])-(b[1]-a[1])*(c[0]-a[0])
def on_segment(a,b,p):return abs(cross(a,b,p))<=1e-12 and min(a[0],b[0])-1e-12<=p[0]<=max(a[0],b[0])+1e-12 and min(a[1],b[1])-1e-12<=p[1]<=max(a[1],b[1])+1e-12
@lru_cache(maxsize=128)
def polygon(raw):
 obj=json.loads(raw)
 if obj.get('type')!='Polygon' or len(obj.get('coordinates',[]))!=1:raise ValueError('Only a single simple Polygon ring is supported; holes require a different engine')
 points=tuple(tuple(number(v) for v in p) for p in obj['coordinates'][0])
 if len(points)<4 or any(len(p)!=2 or not -180<=p[0]<=180 or not -90<=p[1]<=90 for p in points) or points[0]!=points[-1]:raise ValueError('Closed WGS84 coordinate ring required')
 if max(p[0] for p in points)-min(p[0] for p in points)>180:raise ValueError('Antimeridian polygons are outside this local planar reference')
 area=sum(a[0]*b[1]-b[0]*a[1] for a,b in zip(points,points[1:]))
 if abs(area)<1e-12:raise ValueError('Degenerate polygon')
 edges=list(zip(points,points[1:]))
 for i,(a,b) in enumerate(edges):
  if a==b:raise ValueError('Zero-length edge')
  for j,(c,d) in enumerate(edges):
   if j<=i+1 or (i==0 and j==len(edges)-1):continue
   if (cross(a,b,c)*cross(a,b,d)<0 and cross(c,d,a)*cross(c,d,b)<0) or any((on_segment(a,b,c),on_segment(a,b,d),on_segment(c,d,a),on_segment(c,d,b))):raise ValueError('Self-intersecting ring')
 return points

def covers(x,y,raw):
 x,y=number(x),number(y)
 if not -180<=x<=180 or not -90<=y<=90:raise ValueError('Point outside WGS84 coordinate bounds')
 pts=polygon(raw);inside=False
 for a,b in zip(pts,pts[1:]):
  if on_segment(a,b,(x,y)):return 1
  if (a[1]>y)!=(b[1]>y) and x<(b[0]-a[0])*(y-a[1])/(b[1]-a[1])+a[0]:inside=not inside
 return int(inside)
def connection():
 db=sqlite3.connect(':memory:');db.row_factory=sqlite3.Row
 db.create_function('st_covers_local',3,covers,deterministic=True)
 db.executescript((HERE/'detention_schema.sql').read_text(encoding='utf-8-sig'))
 return db
SQUARE=json.dumps({'type':'Polygon','coordinates':[[[-94,42],[-93,42],[-93,43],[-94,43],[-94,42]]]})
def fixture(db,rows=10000):
 db.execute('INSERT INTO geofences VALUES (?,?,?,?)',('fixture','yard',SQUARE,120))
 events=[]
 for i in range(rows):
  asset=f'asset-{i//100:05}';k=i%100;inside=(k%10) in range(1,8)
  events.append(('fixture',asset,str(i),k*60,-93.5 if inside else -92.5,42.5))
 db.executemany('INSERT INTO breadcrumbs VALUES (?,?,?,?,?,?)',events);db.commit()
def audit(db,max_gap_s=180):
 if isinstance(max_gap_s,bool) or not isinstance(max_gap_s,int) or max_gap_s<=0:raise ValueError('Positive integer maximum gap required')
 return [dict(r) for r in db.execute((HERE/'detention_leakage.sql').read_text(encoding='utf-8-sig'),{'max_gap_s':max_gap_s})]
def main():
 ap=argparse.ArgumentParser();ap.add_argument('--benchmark-rows',type=int,default=20000);ap.add_argument('--output');a=ap.parse_args()
 if not 1<=a.benchmark_rows<=1000000:raise ValueError('Fixture row count must be between 1 and 1000000')
 with connection() as db:
  fixture(db,a.benchmark_rows);start=time.perf_counter();results=audit(db);elapsed=time.perf_counter()-start
  report={'fixture':'Synthetic local test data, not customers or operational revenue','rows':a.benchmark_rows,'visit_candidates':len(results),'query_seconds':round(elapsed,6),'sqlite_version':sqlite3.sqlite_version,'boundary':'Local planar single-ring geometry; bounded-gap proxy; reviewed candidates only','sample':results[:2]}
 if a.output:
  p=Path(a.output);p.parent.mkdir(parents=True,exist_ok=True);p.write_text(json.dumps(report,indent=2),encoding='utf-8')
 print(json.dumps(report,indent=2))
if __name__=='__main__':main()
