from __future__ import annotations

import asyncio
import math
import random
from datetime import datetime, timezone

DEFAULT_ENDPOINT = "opc.tcp://127.0.0.1:4840/VirtualPhServer"
DEFAULT_BIND_ENDPOINT = "opc.tcp://0.0.0.0:4840/VirtualPhServer"
DEFAULT_NAMESPACE_URI = "https://cogniflow.org/opcua/virtual"
DEFAULT_DEVICE_NAME = "VirtualPhMeter01"
DEFAULT_PH_NODE = "ns=2;s=VirtualPhMeter01.pH"
DEFAULT_TEMP_NODE = "ns=2;s=VirtualPhMeter01.Temperature"
DEFAULT_TIMESTAMP_NODE = "ns=2;s=VirtualPhMeter01.Timestamp"


async def run_server(
    loop_interval: float = 1.0,
    endpoint: str = DEFAULT_ENDPOINT,
    bind_endpoint: str = DEFAULT_BIND_ENDPOINT,
    namespace_uri: str = DEFAULT_NAMESPACE_URI,
    device_name: str = DEFAULT_DEVICE_NAME,
    ph_node: str = DEFAULT_PH_NODE,
    temp_node: str = DEFAULT_TEMP_NODE,
    timestamp_node: str = DEFAULT_TIMESTAMP_NODE,
    duration: float | None = None,
    stop_event: asyncio.Event | None = None,
    ready_event: asyncio.Event | None = None,
) -> None:
    try:
        from asyncua import Server, ua
    except ImportError as exc:  # pragma: no cover
        raise RuntimeError("asyncua is required; install cf-opcua-server") from exc

    server = Server()
    await server.init()
    server.set_endpoint(bind_endpoint)
    server.set_server_name("Cogniflow Virtual OPC-UA Server")

    idx = await server.register_namespace(namespace_uri)
    objects = server.get_objects_node()
    device = await objects.add_object(idx, device_name)

    ph_nodeid = ua.NodeId(f"{device_name}.pH", idx)
    temp_nodeid = ua.NodeId(f"{device_name}.Temperature", idx)
    ts_nodeid = ua.NodeId(f"{device_name}.Timestamp", idx)

    ph_var = await device.add_variable(ph_nodeid, "pH", 7.0)
    temp_var = await device.add_variable(temp_nodeid, "Temperature", 20.0)
    ts_var = await device.add_variable(ts_nodeid, "Timestamp", "")

    await ph_var.set_writable()
    await temp_var.set_writable()
    await ts_var.set_writable()

    try:
        await server.start()
    except Exception:
        if ready_event:
            ready_event.set()
        raise

    print(f"OPC UA virtual server running at {bind_endpoint}")
    print("Suggested client endpoint:", endpoint)
    print("Nodes:")
    print(f"  pH          -> {ph_node}")
    print(f"  Temperature -> {temp_node}")
    print(f"  Timestamp   -> {timestamp_node}")
    print(f"Update interval: {loop_interval} s")
    if ready_event:
        ready_event.set()

    start_time = asyncio.get_event_loop().time()
    try:
        while True:
            if stop_event is not None and stop_event.is_set():
                break
            if duration is not None and duration > 0:
                if asyncio.get_event_loop().time() - start_time >= duration:
                    break
            t = asyncio.get_event_loop().time() - start_time
            ph = 7.0 + 0.05 * math.sin(t / 30.0) + random.gauss(0, 0.01)
            temp = 20.0 + 0.01 * t + random.gauss(0, 0.05)
            ts = datetime.now(timezone.utc).isoformat()

            await ph_var.write_value(ph)
            await temp_var.write_value(temp)
            await ts_var.write_value(ts)

            await asyncio.sleep(loop_interval)
    finally:
        await server.stop()
