"""Diagnose-Script für DTC HISTORICAL_ORDER_FILL_RESPONSE Paket-Struktur.

Startet SC, verbindet via DTC, empfängt Fill-Pakete und analysiert
die Byte-Struktur um den Preis-Offset zu finden.

Aufruf: python3 test_dtc_diag.py [account] [num_days]
"""

import socket
import struct
import sys
import time
from datetime import datetime

# ── DTC Konstanten ──────────────────────────────────────────────────────────
_LOGON_REQUEST  = 1
_LOGON_RESPONSE = 2
_HEARTBEAT      = 3
_HIST_FILLS_REQ = 303
_HIST_FILL_RESP = 304
_PROTOCOL_VER   = 8

def _s(text, n):
    b = (text or "").encode("ascii", errors="replace")[:n]
    return b.ljust(n, b"\x00")

def _build_logon():
    fmt = "<HHi32s32s64siiii32s64s32si"
    size = struct.calcsize(fmt)
    return struct.pack(fmt,
        size, _LOGON_REQUEST, _PROTOCOL_VER,
        _s("", 32), _s("", 32), _s("GizerBridge-Diag", 64),
        0, 0, 10, 0, _s("", 32), _s("", 64), _s("diag", 32), 0)

def _build_hist_req(req_id, account, num_days):
    fmt = "<HHi32si32sB7xq"
    size = struct.calcsize(fmt)
    return struct.pack(fmt,
        size, _HIST_FILLS_REQ,
        req_id, _s("", 32), num_days, _s(account, 32), 1, 0)

def _recv_msg(sock, buf):
    while True:
        if len(buf) >= 4:
            size = struct.unpack_from("<H", buf, 0)[0]
            if size >= 4 and len(buf) >= size:
                msg = bytes(buf[:size])
                return msg, buf[size:]
        try:
            chunk = sock.recv(65536)
            if not chunk:
                raise ConnectionError("Verbindung geschlossen")
            buf += chunk
        except socket.timeout:
            return None, buf

def _unpack_str(data, offset, n):
    return data[offset:offset+n].rstrip(b"\x00").decode("ascii", errors="replace")

def _ts(raw):
    try:
        return datetime.fromtimestamp(raw).strftime("%Y-%m-%d %H:%M:%S") if 0 < raw < 4_102_444_800 else f"invalid({raw})"
    except Exception:
        return f"error({raw})"

def hex_dump(data, label=""):
    """Formatierter Hex-Dump mit ASCII-Darstellung."""
    print(f"\n{'='*60}")
    if label:
        print(f"  {label}")
    print(f"  Länge: {len(data)} Bytes")
    print(f"{'='*60}")
    for i in range(0, len(data), 16):
        chunk = data[i:i+16]
        hex_part  = " ".join(f"{b:02X}" for b in chunk)
        ascii_part = "".join(chr(b) if 32 <= b < 127 else "." for b in chunk)
        print(f"  {i:4d} ({i:04X}): {hex_part:<48}  |{ascii_part}|")

def analyze_fill_packet(data, fill_num):
    """Umfassende Analyse eines HIST_FILL_RESP Pakets."""
    n = len(data)
    print(f"\n{'#'*60}")
    print(f"  FILL-PAKET #{fill_num}  ({n} Bytes)")
    print(f"{'#'*60}")

    # Bekannte Felder ausgeben
    print("\n--- Bestätigte Felder ---")
    print(f"  [0]   Size    (uint16) = {struct.unpack_from('<H', data, 0)[0]}")
    print(f"  [2]   Type    (uint16) = {struct.unpack_from('<H', data, 2)[0]}")
    print(f"  [4]   ReqID   (int32)  = {struct.unpack_from('<i', data, 4)[0]}")
    if n >= 16:
        print(f"  [8]   TotalMsg(int32)  = {struct.unpack_from('<i', data, 8)[0]}")
        print(f"  [12]  MsgNum  (int32)  = {struct.unpack_from('<i', data, 12)[0]}")
    if n >= 80:
        sym = _unpack_str(data, 16, 64)
        print(f"  [16]  Symbol  (ch[64]) = '{sym}'")
    if n >= 96:
        exch = _unpack_str(data, 80, 16)
        print(f"  [80]  Exchange(ch[16]) = '{exch}'")
    if n >= 132:
        bs = struct.unpack_from("<I", data, 128)[0]
        print(f"  [128] BuySell (uint32) = {bs} ({'BUY' if bs==1 else 'SELL' if bs==2 else '???'})")
    if n >= 152:
        dt_raw = struct.unpack_from("<q", data, 144)[0]
        print(f"  [144] DateTime(int64)  = {dt_raw} = {_ts(dt_raw)}")
    if n >= 160:
        qty = struct.unpack_from("<d", data, 152)[0]
        print(f"  [152] Volume  (double) = {qty}")
    if n >= 192:
        tid = _unpack_str(data, 160, 32)
        print(f"  [160] TradeID (ch[32]) = '{tid}'")
    if n >= 256:
        acc = _unpack_str(data, 224, 32)
        print(f"  [224] Account (ch[32]) = '{acc}'")

    # Unbekannte Zone 96-128 analysieren
    print("\n--- Zone 96-128 (UNBEKANNT, möglicherweise Preis) ---")
    for off in range(96, min(128, n-3), 4):
        if off + 4 <= n:
            as_uint32  = struct.unpack_from("<I", data, off)[0]
            as_int32   = struct.unpack_from("<i", data, off)[0]
            as_float32 = struct.unpack_from("<f", data, off)[0]
            as_ascii   = data[off:off+4].decode("ascii", errors="replace")
            print(f"  [{off:3d}] uint32={as_uint32:10d}  int32={as_int32:10d}  float32={as_float32:12.4f}  ascii='{as_ascii}'")

    # Zone 128-160 analysieren (zwischen BuySell und TradeID)
    print("\n--- Zone 128-160 (zwischen BuySell und DateTime/Volume) ---")
    for off in range(128, min(160, n-3), 4):
        if off + 4 <= n:
            as_uint32  = struct.unpack_from("<I", data, off)[0]
            as_int32   = struct.unpack_from("<i", data, off)[0]
            as_float32 = struct.unpack_from("<f", data, off)[0]
            as_ascii   = data[off:off+4].decode("ascii", errors="replace")
            label = ""
            if off == 128: label = " ← BuySell (bestätigt)"
            if off == 144: label = " ← DateTime (bestätigt)"
            if off == 152: label = " ← Volume (bestätigt)"
            print(f"  [{off:3d}] uint32={as_uint32:10d}  int32={as_int32:10d}  float32={as_float32:12.4f}  ascii='{as_ascii}'{label}")

    # Doubles-Scan über gesamtes Paket
    print("\n--- Doubles (alle 8-Byte-ausgerichteten Offsets) ---")
    for off in range(0, n-7, 8):
        val = struct.unpack_from("<d", data, off)[0]
        tag = ""
        if 1.0 <= val <= 10.0:   tag = " ← Lots?"
        if 5000 <= val <= 30000:  tag = " ← PREIS? (Futures-Range)"
        if 1_600_000_000 <= val <= 2_000_000_000: tag = " ← Timestamp als double?"
        if tag or (off in (96, 104, 112, 120, 136, 152)):
            print(f"  [{off:3d}] double = {val:20.6f}{tag}")

    # Alle uint32 die MNQ-Preis in Ticks sein könnten (80000-100000)
    print("\n--- uint32 im Bereich 50000-120000 (Preis in Ticks à 0.25?) ---")
    for off in range(0, n-3, 4):
        val = struct.unpack_from("<I", data, off)[0]
        if 50000 <= val <= 120000:
            print(f"  [{off:3d}] uint32 = {val}  → Preis = {val * 0.25:.2f}")

    # uint32 im Bereich für MNQ-Preis * 100
    print("\n--- uint32 im Bereich 1500000-3000000 (Preis * 100?) ---")
    for off in range(0, n-3, 4):
        val = struct.unpack_from("<I", data, off)[0]
        if 1_500_000 <= val <= 3_000_000:
            print(f"  [{off:3d}] uint32 = {val}  → Preis/100 = {val / 100:.2f}")

    # int64 Scan (könnte Preis als Festkomma sein)
    print("\n--- int64 im Bereich 50000-5000000 (Preis-Integer?) ---")
    for off in range(0, n-7, 8):
        val = struct.unpack_from("<q", data, off)[0]
        if 50_000 <= val <= 5_000_000:
            print(f"  [{off:3d}] int64 = {val}  → /4={val/4:.2f}  /100={val/100:.2f}  /400={val/400:.4f}")


def main():
    account  = sys.argv[1] if len(sys.argv) > 1 else ""
    num_days = int(sys.argv[2]) if len(sys.argv) > 2 else 30

    print(f"DTC Diagnose-Tool")
    print(f"Account: '{account}', Tage: {num_days}")
    print(f"Verbinde zu localhost:11099 ...")

    try:
        sock = socket.socket()
        sock.settimeout(10)
        sock.connect(("localhost", 11099))
        sock.settimeout(5)
        print("Verbunden!")
    except Exception as e:
        print(f"FEHLER: {e}")
        return

    buf = b""

    # Logon
    sock.sendall(_build_logon())
    for _ in range(20):
        msg, buf = _recv_msg(sock, buf)
        if msg is None:
            continue
        mtype = struct.unpack_from("<H", msg, 2)[0]
        if mtype == _LOGON_RESPONSE:
            result = struct.unpack_from("<i", msg, 8)[0]
            text   = _unpack_str(msg, 12, 96)
            print(f"Logon-Antwort: result={result}, text='{text}'")
            if result != 1:
                print("FEHLER: Logon abgelehnt")
                return
            break

    # Historical fills request
    req_id = int(time.time() * 1000) & 0x7FFFFFFF
    sock.sendall(_build_hist_req(req_id, account, num_days))
    print(f"\nHistorical-Fills-Anfrage gesendet (req_id={req_id})")

    fill_count = 0
    raw_packets = []

    deadline = time.time() + 20
    while time.time() < deadline:
        msg, buf = _recv_msg(sock, buf)
        if msg is None:
            continue
        mtype = struct.unpack_from("<H", msg, 2)[0]
        if mtype != _HIST_FILL_RESP:
            continue

        fill_count += 1
        raw_packets.append(msg)

        # Zeige die ersten 3 und letzten 2 Pakete im Detail
        if fill_count <= 3 or (fill_count <= 5 and len(msg) < 100):
            analyze_fill_packet(msg, fill_count)

        # Prüfe NoOrderFills an verschiedenen Offsets
        for check_off in [256, 257, 260, 264, 188, 180]:
            if len(msg) > check_off:
                val = struct.unpack_from("<B", msg, check_off)[0]
                if val == 1:
                    print(f"\n>>> NoOrderFills=1 gefunden bei Offset {check_off}! (fill_count={fill_count})")

        # Letztes Paket immer anzeigen (NoOrderFills-Paket)
        if fill_count == 1:
            print(f"\n=== ERSTES PAKET (hex dump) ===")
            hex_dump(msg)

    print(f"\n\nGesamt empfangene Fill-Pakete: {fill_count}")

    if raw_packets:
        print("\n=== LETZTES PAKET (hex dump) ===")
        hex_dump(raw_packets[-1], f"Paket #{fill_count}")

        if len(raw_packets) >= 2:
            print("\n=== VERGLEICH: Preis-relevante Bytes in allen Paketen ===")
            print(f"  {'Offset':<8} ", end="")
            for i in range(min(len(raw_packets), 12)):
                print(f"{'Paket'+str(i+1):>12}", end="")
            print()

            # Zeige uint32-Werte für Offsets 96-200
            for off in range(88, 200, 4):
                row = f"  [{off:3d}]   "
                interesting = False
                vals = []
                for pkt in raw_packets[:12]:
                    if len(pkt) > off + 3:
                        v = struct.unpack_from("<I", pkt, off)[0]
                        vals.append(v)
                        row += f"{v:>12}"
                    else:
                        row += f"{'N/A':>12}"

                # Interessant wenn Werte variieren (könnten Preise sein)
                if vals and max(vals) != min(vals):
                    interesting = True
                if off in (128, 144, 152, 160):
                    interesting = True

                if interesting:
                    print(row + "  ← variiert!" if max(vals) != min(vals) else row)

    sock.close()
    print("\nFertig.")

if __name__ == "__main__":
    main()
