#!/usr/bin/env python3
"""Verify GDR packet DATA OUT bytes observed through DF90..DF97 reads.

This is the host->drive complement to analyze_gdr_chunked_data_in.py.  The
firmware helper reads one byte from DF90..DF97 at fixed PCs; in packet DATA OUT
transfers those reads form an 8-lane FIFO.  Reconstruct the first N bytes in
trace order and compare them with the expected host payload.
"""
from __future__ import annotations

import argparse
import csv
import hashlib
from pathlib import Path

DF90_PCS = {
    0x90009A59, 0x90009A5F, 0x90009A65, 0x90009A6B,
    0x90009A71, 0x90009A77, 0x90009A7D, 0x90009A83,
}
DF90_ADDRS = set(range(0x0000DF90, 0x0000DF98))


def parse_int(s: str) -> int:
    return int(str(s).strip(), 0)


def parse_hex_bytes_text(text: str) -> bytes:
    toks = text.replace(',', ' ').replace(';', ' ').replace('\n', ' ').split()
    out = []
    for t in toks:
        if not t:
            continue
        try:
            out.append(int(t, 0) & 0xFF)
        except ValueError:
            out.append(int(t, 16) & 0xFF)
    return bytes(out)


def load_expected(args: argparse.Namespace) -> bytes:
    if args.expected_file:
        return Path(args.expected_file).read_bytes()
    if args.expected_hex:
        return parse_hex_bytes_text(' '.join(args.expected_hex))
    raise SystemExit('[-] provide --expected-file or --expected-hex')


def reconstruct(trace_csv: Path) -> bytes:
    vals = []
    with trace_csv.open('r', encoding='utf-8', errors='replace', newline='') as fh:
        reader = csv.DictReader(fh)
        for row in reader:
            if row.get('event') != 'watch_read':
                continue
            try:
                pc = parse_int(row.get('pc', '0'))
                addr = parse_int(row.get('addr', '0'))
            except Exception:
                continue
            if pc not in DF90_PCS or addr not in DF90_ADDRS:
                continue
            value = row.get('value', '')
            if value:
                try:
                    vals.append(parse_int(value) & 0xFF)
                    continue
                except Exception:
                    pass
            bh = (row.get('bytes_hex') or '').strip().split()
            if bh:
                vals.append(int(bh[0], 16) & 0xFF)
    return bytes(vals)


def main() -> int:
    ap = argparse.ArgumentParser(description='Verify GDR packet DATA OUT DF90..DF97 reads')
    ap.add_argument('trace_csv')
    ap.add_argument('--expected-file')
    ap.add_argument('--expected-hex', nargs='*')
    ap.add_argument('--out', required=True)
    ap.add_argument('--out-payload')
    args = ap.parse_args()

    expected = load_expected(args)
    actual_stream = reconstruct(Path(args.trace_csv))
    actual_prefix = actual_stream[:len(expected)]
    exact = actual_prefix == expected

    lines = []
    lines.append('# GDR packet DATA OUT verification')
    lines.append('')
    lines.append(f'trace={Path(args.trace_csv).name}')
    lines.append('source_mode=df90_df97_fifo_reads')
    lines.append(f'expected_len={len(expected)}')
    lines.append(f'actual_stream_len={len(actual_stream)}')
    lines.append(f'matched_prefix_len={min(len(actual_stream), len(expected))}')
    lines.append(f'exact_complete_payload={str(exact).lower()}')
    lines.append(f'expected_sha256={hashlib.sha256(expected).hexdigest()}')
    lines.append(f'actual_prefix_sha256={hashlib.sha256(actual_prefix).hexdigest()}')
    lines.append('expected_first64=' + ' '.join(f'{b:02X}' for b in expected[:64]))
    lines.append('actual_first64=' + ' '.join(f'{b:02X}' for b in actual_prefix[:64]))
    if not exact:
        mismatch = None
        for i, (a, b) in enumerate(zip(actual_prefix, expected)):
            if a != b:
                mismatch = i
                break
        if mismatch is None and len(actual_prefix) != len(expected):
            mismatch = len(actual_prefix)
        lines.append(f'mismatch_offset={mismatch}')
    lines.append('')
    lines.append('## Byte samples')
    for i in range(0, min(len(actual_stream), 64), 16):
        chunk = actual_stream[i:i+16]
        lines.append(f'- {i:04X}: ' + ' '.join(f'{b:02X}' for b in chunk))
    lines.append('')

    Path(args.out).write_text('\n'.join(lines), encoding='utf-8')
    if args.out_payload:
        Path(args.out_payload).write_bytes(actual_prefix)
    print(f'[OK] wrote {args.out}')
    if args.out_payload:
        print(f'[OK] wrote {args.out_payload}')
    if not exact:
        raise SystemExit(2)
    return 0


if __name__ == '__main__':
    raise SystemExit(main())
