"""Step080 packet assembly, echo, loopback capture and local source-field exercise."""
import argparse
import json
import secrets
import threading

from scapy.all import IP, TCP, ICMP, Raw, AsyncSniffer, sr1, send
from syn_scan import configure_linux, TARGET


def build():
    packet = IP(src=TARGET, dst=TARGET) / TCP(sport=40000, dport=9999, flags="S") / Raw(b"hello")
    wire = bytes(packet)
    decoded = IP(wire)
    return {"layers": ["IP", "TCP", "Raw"], "beforeLength": packet.len,
            "serializedLength": decoded.len, "byteLength": len(wire),
            "ipChecksum": decoded.chksum, "tcpChecksum": decoded[TCP].chksum,
            "payload": decoded[Raw].load.decode(), "flagsType": type(decoded[TCP].flags).__name__,
            "stringComparisonSupported": decoded[TCP].flags == "S"}


def echo(source=TARGET):
    configure_linux()
    ident = secrets.randbelow(65535) + 1
    answer = sr1(IP(src=source, dst=TARGET) / ICMP(type=8, id=ident, seq=1), timeout=2, verbose=0)
    matched = (isinstance(answer, IP) and isinstance(answer.payload, ICMP)
               and answer.src == TARGET and answer.dst == source
               and answer[ICMP].type == 0 and answer[ICMP].code == 0
               and answer[ICMP].id == ident and answer[ICMP].seq == 1)
    return {"sentSource": source, "sentDestination": TARGET, "matchingEchoReply": bool(matched),
            "replySource": answer.src if isinstance(answer, IP) else None,
            "replyDestination": answer.dst if isinstance(answer, IP) else None,
            "replySummary": answer.summary() if answer else None}


def capture_echo():
    configure_linux()
    ident = secrets.randbelow(65535) + 1
    ready = threading.Event()
    sniffer = AsyncSniffer(iface="lo", store=True, timeout=2,
                          started_callback=ready.set,
                          lfilter=lambda p: p.haslayer(ICMP) and p[ICMP].id == ident
                          and p[IP].src == TARGET and p[IP].dst == TARGET)
    sniffer.start()
    try:
        if not ready.wait(2):
            raise RuntimeError("Capture did not start; check permission and lo interface")
        for seq in range(1, 4):
            send(IP(src=TARGET, dst=TARGET) / ICMP(type=8, id=ident, seq=seq), verbose=0)
        sniffer.join()
    finally:
        if sniffer.running:
            sniffer.stop()
    rows = [{"type": int(p[ICMP].type), "id": int(p[ICMP].id), "seq": int(p[ICMP].seq)}
            for p in sniffer.results]
    unique = sorted({(r["type"], r["id"], r["seq"]) for r in rows})
    return {"sentRequests": 3, "capturedRecords": len(rows), "records": rows,
            "uniqueTypeIdSequence": [list(x) for x in unique]}


def main():
    parser = argparse.ArgumentParser(description=__doc__)
    parser.add_argument("exercise", choices=["build", "echo", "sniff", "source"])
    args = parser.parse_args()
    try:
        result = {"build": build, "echo": echo, "sniff": capture_echo,
                  "source": lambda: echo("127.0.0.2")}[args.exercise]()
    except (PermissionError, OSError, RuntimeError) as exc:
        parser.exit(1, f"Packet exercise failed: {exc}\n")
    print(json.dumps(result, indent=2))


if __name__ == "__main__":
    main()
