""" System libraries """
import serial
import serial.tools.list_ports
import platform
from typing import List
from pydantic import BaseModel, Field, field_validator
from robot.api.deco import keyword


def get_serial_ports() -> List[str]:
    """
    Return a list of valid serial port device names.

    Returns:
        List[str]: A list of available serial port device paths
        (e.g., '/dev/ttyUSB0').
    """
    ports: List[str] = []
    for port in sorted(serial.tools.list_ports.comports()):
        if port.description != "n/a" and port.hwid != "n/a":
            ports.append(port.device)
    return ports


def get_default_port() -> str:
    system = platform.system().lower()
    if "linux" in system:
        return "/dev/ttyUSB0"
    elif "windows" in system:
        return "COM5"
    else:
        return "/dev/ttyUSB0"


class PDUConfig(BaseModel):
    port: str = Field(default_factory=get_default_port)
    baudrate: int = Field(default=115200, gt=0)
    timeout: int = Field(default=3, ge=1)
    sockets: int = Field(default=8, ge=1)


class PDUCommand(BaseModel):
    cmd: str

    @field_validator("cmd")
    def validate_command(cls, v):
        if not v.startswith(("S", "C", "PM")):
            raise ValueError("Invalid PDU command. Must start with S, C, PM.")
        return v


class ZSPDU:
    def __init__(self, config: PDUConfig) -> None:
        self.config = config
        self.sockets: int = config.sockets
        self.ser: serial.Serial | None = None
        self.open_port(config.port)

    @keyword("Open Port")
    def open_port(self, port: str) -> str:
        """Open a specific serial port."""
        if self.ser and self.ser.is_open:
            self.ser.close()
        try:
            self.ser = serial.Serial(
                port=port,
                baudrate=self.config.baudrate,
                timeout=self.config.timeout
            )
            return f"Opened port {port}"
        except serial.SerialException as e:
            raise RuntimeError(f"Failed to open serial port {port}: {e}")

    @keyword("Switch Port")
    def switch_port(self, port: str) -> str:
        """Switch to another serial port."""
        return self.open_port(port)

    @keyword("Send PDU Command")
    def send(self, command: PDUCommand) -> str:
        if not self.ser or not self.ser.is_open:
            raise RuntimeError("Serial port is not open")
        self.ser.write((command.cmd + "\r\n").encode("utf-8"))
        #time.sleep(0.25)
        return self.ser.readline().decode("utf-8", errors="ignore").strip()

    @keyword("Turn Socket On")
    def port_on(self, n: int) -> str:
        self.send(PDUCommand(cmd=f"S{n}"))
        return f"Power socket {n} is turned ON"

    @keyword("Turn Socket Off")
    def port_off(self, n: int) -> str:
        self.send(PDUCommand(cmd=f"C{n}"))
        return f"Power socket {n} is turned OFF"

    @keyword("Turn All Sockets On")
    def port_on_all(self) -> str:
        for i in range(1, self.sockets + 1):
            self.port_on(i)
        return "All power sockets are turned ON"

    @keyword("Turn All Sockets Off")
    def port_off_all(self) -> str:
        for i in range(1, self.sockets + 1):
            self.port_off(i)
        return "All power sockets are turned OFF"

    @keyword("Read Current")
    def read_current(self) -> str:
        return self.send(PDUCommand(cmd="PM"))

    @keyword("Close PDU Connection")
    def close(self) -> None:
        if self.ser and self.ser.is_open:
            self.ser.close()


if __name__ == "__main__":
    try:
        # List available serial ports
        available_ports = get_serial_ports()
        print("Available serial ports:", available_ports)

        # Use first available port if present, otherwise fallback
        config = PDUConfig(
            port=available_ports[0] if available_ports else "/dev/ttyUSB0",
            baudrate=115200,
            timeout=3,
            sockets=8
        )

        # Initialize PDU
        pdu = ZSPDU(config)

        # Turn on socket 1
        print(pdu.port_on(1))

        pdu.close()

    except Exception as e:
        print("Error:", e)
