# Copyright (c) 2025 Muhammad Nawaz <m.nawaz2003@gmail.com>
# SPDX-License-Identifier: MIT
"""PTP (Precision Time Protocol) configuration generation.

This module generates skeleton configurations for ptp4l and phc2sys
based on the system snapshot and user configuration.
"""

from __future__ import annotations

import logging
import re
from pathlib import Path
from typing import Dict, List, Optional, Tuple

logger = logging.getLogger(__name__)


def validate_phc(interface: str, snapshot: Dict) -> Tuple[bool, Optional[str]]:
    """Validate that an interface has PHC (PTP Hardware Clock) capability.

    Args:
        interface: Network interface name
        snapshot: System snapshot dictionary

    Returns:
        Tuple of (is_valid, phc_device_path or error_message)
    """
    # Check if interface exists in snapshot
    nics = snapshot.get("nics", [])
    nic_info = None
    for nic in nics:
        if nic.get("name") == interface:
            nic_info = nic
            break

    if not nic_info:
        return False, f"Interface {interface} not found in snapshot"

    # Check for PHC device in sysfs
    phc_path = Path(f"/sys/class/net/{interface}/device/ptp")
    if not phc_path.exists():
        return False, f"No PHC device found for {interface}"

    phc_devs = list(phc_path.iterdir())
    if not phc_devs:
        return False, f"PHC directory empty for {interface}"

    phc_name = phc_devs[0].name  # e.g., "ptp0"
    phc_dev = f"/dev/{phc_name}"

    # Verify device exists
    if not Path(phc_dev).exists():
        return False, f"PHC device {phc_dev} does not exist"

    return True, phc_dev


def get_phc_index(interface: str) -> Optional[int]:
    """Get the PHC index for an interface.

    Args:
        interface: Network interface name

    Returns:
        PHC index (e.g., 0 for ptp0) or None if not found
    """
    phc_path = Path(f"/sys/class/net/{interface}/device/ptp")
    if not phc_path.exists():
        return None

    try:
        phc_devs = list(phc_path.iterdir())
        if phc_devs:
            # Extract number from "ptp0", "ptp1", etc.
            match = re.search(r"ptp(\d+)", phc_devs[0].name)
            if match:
                return int(match.group(1))
    except (OSError, ValueError):
        pass

    return None


def generate_ptp4l_config(
    interface: str,
    phc_device: Optional[str] = None,
    domain: int = 0,
    priority1: int = 128,
    transport: str = "L2",
    delay_mechanism: str = "E2E",
) -> str:
    """Generate a ptp4l configuration file.

    Args:
        interface: Network interface for PTP
        phc_device: PHC device path (auto-detected if None)
        domain: PTP domain number
        priority1: Clock priority1 value
        transport: Transport mode (L2 or UDPv4)
        delay_mechanism: Delay mechanism (E2E or P2P)

    Returns:
        ptp4l configuration file content
    """
    # Auto-detect PHC device if not provided
    if not phc_device:
        phc_idx = get_phc_index(interface)
        if phc_idx is not None:
            phc_device = f"/dev/ptp{phc_idx}"

    network_transport = "L2" if transport.upper() == "L2" else "UDPv4"

    config = f"""\
# ptp4l configuration generated by lltune
# Interface: {interface}
# PHC Device: {phc_device or 'auto'}

[global]
# Clock settings
clockClass              248
clockAccuracy           0xFE
offsetScaledLogVariance 0xFFFF
domainNumber            {domain}
priority1               {priority1}
priority2               128

# Network settings
network_transport       {network_transport}
delay_mechanism         {delay_mechanism}

# Logging
verbose                 0
use_syslog              1
logging_level           6

# Servo settings (for low latency)
step_threshold          0.0
first_step_threshold    0.0
max_frequency           900000000

# Hardware timestamping
time_stamping           hardware
tx_timestamp_timeout    10

# Interface configuration
[{interface}]
"""

    return config


def generate_phc2sys_config(
    phc_device: str,
    sys_clock: str = "CLOCK_REALTIME",
    sync_interval: float = 1.0,
) -> str:
    """Generate phc2sys systemd service configuration.

    Note: phc2sys doesn't use a config file; this generates a systemd
    service unit or command-line arguments.

    Args:
        phc_device: PHC device path (e.g., /dev/ptp0)
        sys_clock: System clock to sync (CLOCK_REALTIME)
        sync_interval: Sync interval in seconds

    Returns:
        phc2sys command arguments or service configuration
    """
    # Extract device name for service file
    phc_name = Path(phc_device).name  # e.g., "ptp0"
    try:
        interval = float(sync_interval)
        update_rate = max(1, int(round(1.0 / interval))) if interval > 0 else 1
    except (TypeError, ValueError):
        update_rate = 1

    service = f"""\
# phc2sys systemd service generated by lltune
# Synchronizes system clock to PHC: {phc_device}

[Unit]
Description=Synchronize system clock to PTP hardware clock ({phc_name})
Documentation=man:phc2sys(8)
After=lltune-ptp4l.service
Requires=lltune-ptp4l.service

[Service]
Type=simple
ExecStart=/usr/sbin/phc2sys -s {phc_device} -c {sys_clock} -O 0 \\
    -R {update_rate} -w
Restart=on-failure
RestartSec=5

# Security hardening
NoNewPrivileges=yes
ProtectSystem=full
ProtectHome=yes

[Install]
WantedBy=multi-user.target
"""

    return service


def generate_ptp4l_service(interface: str, config_path: str) -> str:
    """Generate ptp4l systemd service unit.

    Args:
        interface: Network interface for PTP
        config_path: Path to ptp4l configuration file

    Returns:
        systemd service unit content
    """
    service = f"""\
# ptp4l systemd service generated by lltune
# Interface: {interface}

[Unit]
Description=Precision Time Protocol (PTP) daemon for {interface}
Documentation=man:ptp4l(8)
After=network-online.target
Wants=network-online.target

[Service]
Type=simple
ExecStart=/usr/sbin/ptp4l -f {config_path} -i {interface}
Restart=on-failure
RestartSec=5

# Security hardening
NoNewPrivileges=yes
ProtectSystem=full
ProtectHome=yes

[Install]
WantedBy=multi-user.target
"""

    return service


def write_ptp_configs(
    cfg: Dict,
    snapshot: Dict,
    output_dir: Path,
) -> Dict[str, Path]:
    """Generate and write PTP configuration files.

    Args:
        cfg: lltune configuration dictionary
        snapshot: System snapshot dictionary
        output_dir: Directory to write generated configs

    Returns:
        Dictionary mapping config type to file path
    """
    results = {}
    ptp_cfg = cfg.get("time_sync", {}).get("ptp", {})

    if not isinstance(ptp_cfg, dict):
        logger.warning(
            "time_sync.ptp is not a dict; skipping PTP config generation"
        )
        return results

    interface = ptp_cfg.get("interface")
    if not interface or "TODO" in str(interface):
        logger.info(
            "No PTP interface configured; skipping PTP config generation"
        )
        return results

    # Validate PHC capability
    valid, result = validate_phc(interface, snapshot)
    if not valid:
        logger.warning("PHC validation failed for %s: %s", interface, result)
        return results

    phc_device = result
    logger.info("Validated PHC device for %s: %s", interface, phc_device)

    # Create output directory
    ptp_dir = output_dir / "ptp"
    ptp_dir.mkdir(parents=True, exist_ok=True)

    # Generate ptp4l config
    domain = ptp_cfg.get("domain", 0)
    priority = ptp_cfg.get("priority1", 128)
    transport = ptp_cfg.get("transport", "L2")

    ptp4l_config = generate_ptp4l_config(
        interface=interface,
        phc_device=phc_device,
        domain=domain,
        priority1=priority,
        transport=transport,
    )
    ptp4l_path = ptp_dir / "ptp4l.conf"
    ptp4l_path.write_text(ptp4l_config)
    results["ptp4l_config"] = ptp4l_path
    logger.info("Generated ptp4l config: %s", ptp4l_path)

    # Generate ptp4l service
    ptp4l_service = generate_ptp4l_service(interface, str(ptp4l_path))
    ptp4l_svc_path = ptp_dir / "lltune-ptp4l.service"
    ptp4l_svc_path.write_text(ptp4l_service)
    results["ptp4l_service"] = ptp4l_svc_path
    logger.info("Generated ptp4l service: %s", ptp4l_svc_path)

    # Generate phc2sys service if requested
    if ptp_cfg.get("phc2sys", False):
        sync_interval = ptp_cfg.get("sync_interval", 1.0)
        phc2sys_service = generate_phc2sys_config(
            phc_device=phc_device,
            sync_interval=sync_interval,
        )
        phc2sys_path = ptp_dir / "lltune-phc2sys.service"
        phc2sys_path.write_text(phc2sys_service)
        results["phc2sys_service"] = phc2sys_path
        logger.info("Generated phc2sys service: %s", phc2sys_path)

    # Write README for PTP setup
    readme = f"""\
LLTune PTP Configuration
========================

Generated PTP configuration for interface: {interface}
PHC device: {phc_device}

Files:
- ptp4l.conf: PTP daemon configuration
- lltune-ptp4l.service: systemd service for ptp4l
- lltune-phc2sys.service: systemd service for phc2sys (if enabled)

Installation:
  # Copy service files
  sudo cp {ptp_dir}/lltune-ptp4l.service /etc/systemd/system/
  sudo cp {ptp_dir}/lltune-phc2sys.service /etc/systemd/system/  # if generated

  # Copy config
  sudo mkdir -p /etc/ptp
  sudo cp {ptp_dir}/ptp4l.conf /etc/ptp/

  # Enable and start services
  sudo systemctl daemon-reload
  sudo systemctl enable --now lltune-ptp4l
  sudo systemctl enable --now lltune-phc2sys  # if using phc2sys

Verification:
  # Check PTP status
  sudo pmc -u -b 0 'GET CURRENT_DATA_SET'

  # Check PHC offset
  sudo phc_ctl {phc_device} get

Notes:
- Ensure your network supports PTP (hardware timestamping on switches if using E2E)
- For best results, use PTP-aware switches with boundary clock support
- Monitor offset regularly; values should be < 100ns for HFT use cases
"""
    readme_path = ptp_dir / "README.txt"
    readme_path.write_text(readme)
    results["readme"] = readme_path

    return results


def check_ptp_requirements(snapshot: Dict) -> List[str]:
    """Check system requirements for PTP operation.

    Args:
        snapshot: System snapshot dictionary

    Returns:
        List of warnings/issues found
    """
    issues = []

    # Check for PHC-capable NICs
    phc_nics = []
    for nic in snapshot.get("nics", []):
        name = nic.get("name")
        if name:
            phc_path = Path(f"/sys/class/net/{name}/device/ptp")
            if phc_path.exists() and list(phc_path.iterdir()):
                phc_nics.append(name)

    if not phc_nics:
        issues.append(
            "No PHC-capable NICs found; hardware timestamping not available"
        )

    # Check for ptp4l
    from shutil import which

    if not which("ptp4l"):
        issues.append("ptp4l not found; install linuxptp package")

    if not which("phc2sys"):
        issues.append("phc2sys not found; install linuxptp package")

    if not which("pmc"):
        issues.append("pmc not found; install linuxptp package")

    # Check time sync status
    ts = snapshot.get("time_sync", {})
    if ts.get("ptp_present"):
        logger.info("PTP already appears to be configured on this system")

    return issues
