#!/usr/bin/env python3
from __future__ import annotations

import argparse
import time

from hil_common import configured_port, load_config, main_guard, serial_module


def frame(seconds: str = "00000064", include_energy: bool = True, terminator: bool = True) -> bytes:
    lines = ["/DD3HIL", f"0-0:96.8.0*255({seconds})"]
    if include_energy:
        lines.append("1-0:1.8.0*255(001234.567*kWh)")
    lines.extend([
        "1-0:16.7.0*255(000950*W)",
        "1-0:36.7.0*255(000500*W)",
        "1-0:56.7.0*255(000450*W)",
        "1-0:76.7.0*255(000000*W)",
    ])
    text = "\r\n".join(lines) + "\r\n"
    if terminator:
        text += "!\r\n"
    return text.encode("ascii")


def fixture(name: str) -> list[tuple[bytes, float]]:
    valid = frame()
    fixtures = {
        "valid": [(valid, 0)],
        "timeout": [(b"/DD3HIL\r\n1-0:1.8.0*255(1*kWh)", 2.0)],
        "truncated": [(valid[:40], 2.0)],
        "missing_terminator": [(frame(terminator=False), 2.0)],
        "malformed_obis": [(valid.replace(b"1-0:1.8.0", b"1-0:X.Y.Z"), 0)],
        "missing_required": [(frame(include_energy=False), 0)],
        "invalid_meter_seconds": [(frame(seconds="GGGGGGGG"), 0)],
        "timestamp_rollback": [(frame(seconds="00000100"), 0.2), (frame(seconds="00000080"), 0)],
        "timestamp_jump": [(frame(seconds="00000100"), 0.2), (frame(seconds="00001000"), 0)],
        "oversized": [(b"/" + b"A" * 600 + b"!\r\n", 0)],
        "malformed_then_valid": [(valid.replace(b"1-0:1.8.0", b"1-0:X.Y.Z"), 0.2), (valid, 0)],
    }
    if name == "slow_inter_byte":
        return [(bytes([byte]), 0.02) for byte in valid]
    return fixtures[name]


def main() -> int:
    choices = ["valid", "timeout", "truncated", "missing_terminator", "malformed_obis",
               "missing_required", "invalid_meter_seconds", "timestamp_rollback", "timestamp_jump",
               "oversized", "slow_inter_byte", "malformed_then_valid"]
    parser = argparse.ArgumentParser(description="Drive an explicitly approved isolated 9600 7E1 meter simulator")
    parser.add_argument("fixture", choices=choices)
    args = parser.parse_args()
    config = load_config()
    port = configured_port(config, "meter_simulator", "access")
    serial = serial_module()
    with serial.Serial(str(port.device), 9600, bytesize=serial.SEVENBITS, parity=serial.PARITY_EVEN,
                       stopbits=serial.STOPBITS_ONE, timeout=1, write_timeout=2, exclusive=True) as handle:
        for payload, pause in fixture(args.fixture):
            handle.write(payload)
            handle.flush()
            if pause:
                time.sleep(pause)
    print(f"Sent {args.fixture} through approved isolated simulator {port.device}")
    return 0


if __name__ == "__main__":
    main_guard(main)
