"""Local fault-controlled receiver. Its SQLite table deduplicates business effects."""
import argparse, hashlib, hmac, json, socket, sqlite3, threading, time
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
from delivery_queue import sign

SECRET='demo-signing-secret-change-me'
class Receiver:
    def __init__(self,path):
        self.path=str(path)
        with sqlite3.connect(self.path) as db:
            db.execute('CREATE TABLE IF NOT EXISTS receipts(path TEXT,event_id TEXT,fingerprint TEXT,hits INTEGER,effects INTEGER,PRIMARY KEY(path,event_id))')
    def receive(self,path,event_id,body):
        digest=hashlib.sha256(body).hexdigest()
        with sqlite3.connect(self.path,timeout=10) as db:
            db.execute('BEGIN IMMEDIATE')
            row=db.execute('SELECT fingerprint,hits FROM receipts WHERE path=? AND event_id=?',(path,event_id)).fetchone()
            if row and row[0]!=digest: return None
            hits=row[1]+1 if row else 1
            if row: db.execute('UPDATE receipts SET hits=? WHERE path=? AND event_id=?',(hits,path,event_id))
            else: db.execute('INSERT INTO receipts VALUES(?,?,?,?,?)',(path,event_id,digest,1,1))
            return hits
    def snapshot(self):
        with sqlite3.connect(self.path) as db:
            db.row_factory=sqlite3.Row
            return [dict(r) for r in db.execute('SELECT path,event_id,hits,effects FROM receipts ORDER BY path,event_id')]

def make_server(receiver,port=9081,secret=SECRET):
    class Handler(BaseHTTPRequestHandler):
        def log_message(self,*args): pass
        def do_POST(self):
            try:
                n=int(self.headers.get('Content-Length','0'))
                if n<=0 or n>65536: raise ValueError()
                body=self.rfile.read(n); event=json.loads(body); stamp=int(self.headers.get('X-Webhook-Timestamp','0'))
                expected=sign(secret,stamp,body)
                if abs(time.time()-stamp)>300 or not hmac.compare_digest(expected,self.headers.get('X-Webhook-Signature','')):
                    self.send_error(401); return
                if event.get('id')!=self.headers.get('X-Event-ID'): self.send_error(400); return
                if self.path=='/reject': self.send_error(422); return
                if self.path=='/redirect':
                    self.send_response(302); self.send_header('Location','http://127.0.0.1:1/never'); self.end_headers(); return
                if self.path=='/fail': self.send_error(503); return
                if self.path=='/slow': time.sleep(.1)
                hits=receiver.receive(self.path,event['id'],body)
                if hits is None: self.send_error(409); return
                if self.path=='/drop-once' and hits==1:
                    self.close_connection=True
                    try: self.connection.shutdown(socket.SHUT_RDWR)
                    except OSError: pass
                    self.connection.close(); return
                data=json.dumps({'accepted':True,'duplicate':hits>1}).encode()
                self.send_response(200); self.send_header('Content-Type','application/json'); self.send_header('Content-Length',str(len(data))); self.end_headers(); self.wfile.write(data)
            except (BrokenPipeError,ConnectionResetError): pass
            except (ValueError,KeyError,json.JSONDecodeError): self.send_error(400)
        def do_GET(self):
            if self.path!='/receipts': self.send_error(404); return
            data=json.dumps(receiver.snapshot()).encode();self.send_response(200);self.send_header('Content-Type','application/json');self.send_header('Content-Length',str(len(data)));self.end_headers();self.wfile.write(data)
    return ThreadingHTTPServer(('127.0.0.1',port),Handler)

if __name__=='__main__':
    p=argparse.ArgumentParser();p.add_argument('--port',type=int,default=9081);p.add_argument('--db',default='receiver.sqlite');args=p.parse_args()
    server=make_server(Receiver(args.db),args.port)
    print(f'Local test receiver: http://127.0.0.1:{server.server_port}/receipts')
    try: server.serve_forever()
    except KeyboardInterrupt: pass
    finally: server.server_close()
