#!/usr/bin/env python3
# -*- coding: utf-8 -*-
'''
RUL.confidence Edge Agent
Подключается к источникам на цехе и отправляет данные в /live/feed.

Режимы:
  csv       - смотреть папку с CSV-файлами
  opcua     - читать теги OPC UA
  modbus    - читать регистры Modbus TCP
  mqtt      - подписываться на MQTT-топик

Обнаружение (только с вашего разрешения и только в вашей сети):
  python rul_agent.py --discover --subnet 192.168.1.0/24

Примеры:
  python rul_agent.py --mode csv --folder /data/csv --equipment pump --asset pump_01
  python rul_agent.py --mode opcua --url opc.tcp://192.168.1.10:4840 --nodes "ns=2;i=2;name=temperature" --equipment pump --asset pump_01
  python rul_agent.py --mode modbus --host 192.168.1.10 --registers 0,1,2 --equipment pump --asset pump_01
  python rul_agent.py --mode mqtt --broker mqtt.example.com --topic plant/+/telemetry --equipment pump --asset pump_01
'''
import argparse
import csv
import glob
import json
import os
import socket
import sys
import time
from urllib.parse import urljoin

API_KEY = os.environ.get("RUL_API_KEY", "YOUR_API_KEY")
SERVER_URL = os.environ.get("RUL_SERVER_URL", "https://rulconf.ru")
CLIENT_ID = os.environ.get("RUL_CLIENT_ID", "YOUR_CLIENT_ID")
ENDPOINT = urljoin(SERVER_URL.rstrip("/") + "/", "live/feed")
PASSPORT_LIFE = os.environ.get("RUL_PASSPORT_LIFE", "")
HOURS_RUN = os.environ.get("RUL_HOURS_RUN", "1000")
PASSPORT_DEFAULTS = {
    "pump": 20000, "cnc_machine": 20000, "laser_machine": 20000,
    "truck": 15000, "bearing": 5000, "machine": 10000,
    "compressor": 12000, "motor": 15000, "fan": 10000,
}


def send(features_dict, equipment, asset_id):
    payload = {
        "api_key": API_KEY,
        "equipment": equipment,
        "equipment_id": asset_id,
        "features_dict": features_dict,
        "passport_life": float(PASSPORT_LIFE or PASSPORT_DEFAULTS.get(equipment, 10000)),
        "hours_run": float(HOURS_RUN or 1000),
    }
    try:
        import requests
        r = requests.post(ENDPOINT, json=payload, timeout=15)
        data = r.json()
        print(f"[{r.status_code}] {asset_id}: RUL={data.get('rul', '?')} риск={data.get('risk', '?')}")
        return r.ok
    except Exception as e:
        print(f"[ERROR] {asset_id}: {e}")
        return False


def run_csv(folder, equipment, asset_id, interval):
    done_dir = os.path.join(folder, "done")
    os.makedirs(done_dir, exist_ok=True)
    processed = set()
    print(f"Слежу за папкой {folder}...")
    while True:
        files = sorted(glob.glob(os.path.join(folder, "*.csv")))
        for path in files:
            if path in processed:
                continue
            try:
                with open(path, "r", encoding="utf-8-sig") as f:
                    reader = csv.DictReader(f)
                    for row in reader:
                        features = {}
                        for k, v in row.items():
                            if k and v not in (None, ""):
                                try:
                                    features[k] = float(v)
                                except ValueError:
                                    features[k] = v
                        if features:
                            send(features, equipment, asset_id)
                processed.add(path)
                dest = os.path.join(done_dir, os.path.basename(path))
                os.rename(path, dest)
                print(f"Обработан {path} -> {dest}")
            except Exception as e:
                print(f"[ERROR] {path}: {e}")
        time.sleep(interval)


def run_opcua(url, nodes, equipment, asset_id, interval):
    try:
        from opcua import Client
    except ImportError:
        print("Установите python-opcua: pip install opcua")
        sys.exit(1)
    client = Client(url)
    client.connect()
    print(f"OPC UA подключен: {url}")
    try:
        while True:
            features = {}
            for node_spec in nodes:
                parts = node_spec.split(";")
                node_id = parts[0].strip()
                name = node_id
                for p in parts[1:]:
                    if p.strip().lower().startswith("name="):
                        name = p.strip().split("=", 1)[1]
                        break
                node = client.get_node(node_id)
                try:
                    features[name] = float(node.get_value())
                except Exception:
                    features[name] = node.get_value()
            if features:
                send(features, equipment, asset_id)
            time.sleep(interval)
    finally:
        client.disconnect()


def run_modbus(host, port, registers, equipment, asset_id, interval):
    try:
        from pymodbus.client import ModbusTcpClient
    except ImportError:
        print("Установите pymodbus: pip install pymodbus")
        sys.exit(1)
    client = ModbusTcpClient(host, port=port)
    if not client.connect():
        print(f"Не удалось подключиться к Modbus {host}:{port}")
        sys.exit(1)
    print(f"Modbus подключен: {host}:{port}")
    try:
        while True:
            features = {}
            for reg in registers:
                resp = client.read_holding_registers(reg, 1)
                if resp and not resp.isError():
                    features[f"register_{reg}"] = float(resp.registers[0])
            if features:
                send(features, equipment, asset_id)
            time.sleep(interval)
    finally:
        client.close()


def run_mqtt(broker, port, topic, equipment, asset_id):
    try:
        import paho.mqtt.client as mqtt
    except ImportError:
        print("Установите paho-mqtt: pip install paho-mqtt")
        sys.exit(1)

    def on_message(client, userdata, msg):
        try:
            payload = json.loads(msg.payload.decode())
            if isinstance(payload, dict):
                send(payload, equipment, asset_id)
            else:
                print("[WARN] MQTT payload is not JSON object")
        except Exception as e:
            print(f"[ERROR] MQTT: {e}")

    client = mqtt.Client()
    client.on_message = on_message
    client.connect(broker, port, 60)
    client.subscribe(topic)
    print(f"MQTT подписан: {broker}:{port} topic={topic}")
    client.loop_forever()


def discover(subnet):
    print("ВНИМАНИЕ: сканирование выполняется только в сети, которой вы владеете или управляете.")
    print("Никакие данные не читаются — только проверяются открытые порты.")
    import ipaddress
    try:
        net = ipaddress.ip_network(subnet, strict=False)
    except ValueError:
        print("Неверная подсеть. Пример: 192.168.1.0/24")
        sys.exit(1)
    services = {
        4840: "OPC UA",
        502: "Modbus TCP",
        1883: "MQTT",
        8883: "MQTT (TLS)",
        5007: "MTConnect",
        80: "HTTP/REST",
        8080: "HTTP/REST",
    }
    found = []
    hosts = list(net.hosts())[:254]
    for host in hosts:
        ip = str(host)
        for port, svc in services.items():
            try:
                s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
                s.settimeout(0.15)
                s.connect((ip, port))
                s.close()
                found.append((ip, port, svc))
                print(f"  {ip}:{port} -> {svc}")
                break
            except (socket.timeout, ConnectionRefusedError, OSError):
                pass
    if not found:
        print("Ничего не найдено.")
    else:
        print(f"\nНайдено {len(found)} хостов. Добавьте нужные вручную через конфиг агента.")
    return found


def main():
    parser = argparse.ArgumentParser(description="RUL.confidence Edge Agent")
    parser.add_argument("--mode", choices=["csv", "opcua", "modbus", "mqtt"], help="Режим работы")
    parser.add_argument("--discover", action="store_true", help="Обнаружить источники в локальной сети")
    parser.add_argument("--subnet", default=None, help="Подсеть для обнаружения, например 192.168.1.0/24")
    parser.add_argument("--folder", default=".", help="[csv] папка с CSV")
    parser.add_argument("--url", default="opc.tcp://localhost:4840", help="[opcua] URL сервера")
    parser.add_argument("--nodes", default="", help="[opcua] node ids: ns=2;i=2;name=temperature,...")
    parser.add_argument("--host", default="localhost", help="[modbus] хост")
    parser.add_argument("--port", type=int, default=502, help="[modbus/mqtt] порт")
    parser.add_argument("--registers", default="0", help="[modbus] регистры через запятую")
    parser.add_argument("--broker", default="localhost", help="[mqtt] брокер")
    parser.add_argument("--topic", default="#", help="[mqtt] топик")
    parser.add_argument("--equipment", default="machine", help="Тип оборудования")
    parser.add_argument("--asset", default="edge_asset", help="ID актива")
    parser.add_argument("--interval", type=int, default=60, help="Интервал опроса, сек")
    args = parser.parse_args()

    if args.discover:
        subnet = args.subnet or input("Введите подсеть (например 192.168.1.0/24): ").strip()
        discover(subnet)
        return
    if not args.mode:
        parser.print_help()
        return
    if args.mode == "csv":
        run_csv(args.folder, args.equipment, args.asset, args.interval)
    elif args.mode == "opcua":
        nodes = [n.strip() for n in args.nodes.split(",") if n.strip()]
        run_opcua(args.url, nodes, args.equipment, args.asset, args.interval)
    elif args.mode == "modbus":
        regs = [int(r.strip()) for r in args.registers.split(",") if r.strip()]
        run_modbus(args.host, args.port, regs, args.equipment, args.asset, args.interval)
    elif args.mode == "mqtt":
        run_mqtt(args.broker, args.port, args.topic, args.equipment, args.asset)


if __name__ == "__main__":
    main()
