"""System metrics collector using psutil."""

from __future__ import annotations

from typing import TYPE_CHECKING

import psutil
import structlog

from curadise_agent.collectors.base import DomainCollector
from curadise_agent.models.domain.metric import DomainMetric, MetricType

if TYPE_CHECKING:
    from curadise_agent.config.schema import CollectorConfig

log = structlog.get_logger(__name__)


class SystemCollector(DomainCollector):
    """
    Collects system metrics using psutil.

    Metrics include:
    - CPU usage (overall and per-core)
    - Memory usage
    - Disk usage and I/O
    - Network I/O
    - System load
    """

    def __init__(
        self,
        name: str = "system",
        config: CollectorConfig | None = None,
        tags: dict[str, str] | None = None,
    ) -> None:
        """Initialize system collector."""
        super().__init__(name=name, config=config, tags=tags)

        # Configuration options
        self._collect_per_cpu = self.options.get("per_cpu", False)
        self._collect_per_disk = self.options.get("per_disk", True)
        self._collect_per_nic = self.options.get("per_nic", True)

    async def collect(self) -> list[DomainMetric]:
        """Collect system metrics."""
        metrics: list[DomainMetric] = []

        # CPU metrics
        metrics.extend(self._collect_cpu())

        # Memory metrics
        metrics.extend(self._collect_memory())

        # Disk metrics
        metrics.extend(self._collect_disk())

        # Network metrics
        metrics.extend(self._collect_network())

        # System load
        metrics.extend(self._collect_load())

        return metrics

    def _collect_cpu(self) -> list[DomainMetric]:
        """Collect CPU metrics."""
        metrics: list[DomainMetric] = []

        # Overall CPU percent
        cpu_percent = psutil.cpu_percent(interval=None)
        metrics.append(
            DomainMetric(
                name="system.cpu.usage_percent",
                value=cpu_percent,
                metric_type=MetricType.GAUGE,
                unit="percent",
                description="CPU usage percentage",
            )
        )

        # CPU times
        cpu_times = psutil.cpu_times_percent(interval=None)
        metrics.extend(
            [
                DomainMetric(
                    name="system.cpu.user_percent",
                    value=cpu_times.user,
                    metric_type=MetricType.GAUGE,
                    unit="percent",
                ),
                DomainMetric(
                    name="system.cpu.system_percent",
                    value=cpu_times.system,
                    metric_type=MetricType.GAUGE,
                    unit="percent",
                ),
                DomainMetric(
                    name="system.cpu.idle_percent",
                    value=cpu_times.idle,
                    metric_type=MetricType.GAUGE,
                    unit="percent",
                ),
            ]
        )

        # Per-CPU if enabled
        if self._collect_per_cpu:
            per_cpu = psutil.cpu_percent(interval=None, percpu=True)
            for i, percent in enumerate(per_cpu):
                metrics.append(
                    DomainMetric(
                        name="system.cpu.core_usage_percent",
                        value=percent,
                        metric_type=MetricType.GAUGE,
                        unit="percent",
                        tags={"core": str(i)},
                    )
                )

        # CPU count
        metrics.append(
            DomainMetric(
                name="system.cpu.count",
                value=float(psutil.cpu_count() or 1),
                metric_type=MetricType.GAUGE,
                unit="cores",
            )
        )

        return metrics

    def _collect_memory(self) -> list[DomainMetric]:
        """Collect memory metrics."""
        metrics: list[DomainMetric] = []

        mem = psutil.virtual_memory()
        metrics.extend(
            [
                DomainMetric(
                    name="system.memory.total_bytes",
                    value=float(mem.total),
                    metric_type=MetricType.GAUGE,
                    unit="bytes",
                ),
                DomainMetric(
                    name="system.memory.available_bytes",
                    value=float(mem.available),
                    metric_type=MetricType.GAUGE,
                    unit="bytes",
                ),
                DomainMetric(
                    name="system.memory.used_bytes",
                    value=float(mem.used),
                    metric_type=MetricType.GAUGE,
                    unit="bytes",
                ),
                DomainMetric(
                    name="system.memory.usage_percent",
                    value=mem.percent,
                    metric_type=MetricType.GAUGE,
                    unit="percent",
                ),
            ]
        )

        # Swap
        swap = psutil.swap_memory()
        metrics.extend(
            [
                DomainMetric(
                    name="system.swap.total_bytes",
                    value=float(swap.total),
                    metric_type=MetricType.GAUGE,
                    unit="bytes",
                ),
                DomainMetric(
                    name="system.swap.used_bytes",
                    value=float(swap.used),
                    metric_type=MetricType.GAUGE,
                    unit="bytes",
                ),
                DomainMetric(
                    name="system.swap.usage_percent",
                    value=swap.percent,
                    metric_type=MetricType.GAUGE,
                    unit="percent",
                ),
            ]
        )

        return metrics

    def _collect_disk(self) -> list[DomainMetric]:
        """Collect disk metrics."""
        metrics: list[DomainMetric] = []

        # Disk usage per partition
        if self._collect_per_disk:
            for partition in psutil.disk_partitions(all=False):
                try:
                    usage = psutil.disk_usage(partition.mountpoint)
                    tags = {
                        "mountpoint": partition.mountpoint,
                        "device": partition.device,
                        "fstype": partition.fstype,
                    }
                    metrics.extend(
                        [
                            DomainMetric(
                                name="system.disk.total_bytes",
                                value=float(usage.total),
                                metric_type=MetricType.GAUGE,
                                unit="bytes",
                                tags=tags,
                            ),
                            DomainMetric(
                                name="system.disk.used_bytes",
                                value=float(usage.used),
                                metric_type=MetricType.GAUGE,
                                unit="bytes",
                                tags=tags,
                            ),
                            DomainMetric(
                                name="system.disk.free_bytes",
                                value=float(usage.free),
                                metric_type=MetricType.GAUGE,
                                unit="bytes",
                                tags=tags,
                            ),
                            DomainMetric(
                                name="system.disk.usage_percent",
                                value=usage.percent,
                                metric_type=MetricType.GAUGE,
                                unit="percent",
                                tags=tags,
                            ),
                        ]
                    )
                except (PermissionError, OSError):
                    continue

        # Disk I/O counters
        try:
            disk_io = psutil.disk_io_counters()
            if disk_io:
                metrics.extend(
                    [
                        DomainMetric(
                            name="system.disk.read_bytes_total",
                            value=float(disk_io.read_bytes),
                            metric_type=MetricType.COUNTER,
                            unit="bytes",
                        ),
                        DomainMetric(
                            name="system.disk.write_bytes_total",
                            value=float(disk_io.write_bytes),
                            metric_type=MetricType.COUNTER,
                            unit="bytes",
                        ),
                        DomainMetric(
                            name="system.disk.read_count_total",
                            value=float(disk_io.read_count),
                            metric_type=MetricType.COUNTER,
                            unit="operations",
                        ),
                        DomainMetric(
                            name="system.disk.write_count_total",
                            value=float(disk_io.write_count),
                            metric_type=MetricType.COUNTER,
                            unit="operations",
                        ),
                    ]
                )
        except Exception:
            pass

        return metrics

    def _collect_network(self) -> list[DomainMetric]:
        """Collect network metrics."""
        metrics: list[DomainMetric] = []

        # Overall network I/O
        net_io = psutil.net_io_counters()
        metrics.extend(
            [
                DomainMetric(
                    name="system.network.bytes_sent_total",
                    value=float(net_io.bytes_sent),
                    metric_type=MetricType.COUNTER,
                    unit="bytes",
                ),
                DomainMetric(
                    name="system.network.bytes_recv_total",
                    value=float(net_io.bytes_recv),
                    metric_type=MetricType.COUNTER,
                    unit="bytes",
                ),
                DomainMetric(
                    name="system.network.packets_sent_total",
                    value=float(net_io.packets_sent),
                    metric_type=MetricType.COUNTER,
                    unit="packets",
                ),
                DomainMetric(
                    name="system.network.packets_recv_total",
                    value=float(net_io.packets_recv),
                    metric_type=MetricType.COUNTER,
                    unit="packets",
                ),
                DomainMetric(
                    name="system.network.errors_in_total",
                    value=float(net_io.errin),
                    metric_type=MetricType.COUNTER,
                    unit="errors",
                ),
                DomainMetric(
                    name="system.network.errors_out_total",
                    value=float(net_io.errout),
                    metric_type=MetricType.COUNTER,
                    unit="errors",
                ),
            ]
        )

        # Per-interface if enabled
        if self._collect_per_nic:
            net_io_per_nic = psutil.net_io_counters(pernic=True)
            for nic, counters in net_io_per_nic.items():
                # Skip loopback
                if nic.startswith("lo"):
                    continue
                tags = {"interface": nic}
                metrics.extend(
                    [
                        DomainMetric(
                            name="system.network.interface_bytes_sent_total",
                            value=float(counters.bytes_sent),
                            metric_type=MetricType.COUNTER,
                            unit="bytes",
                            tags=tags,
                        ),
                        DomainMetric(
                            name="system.network.interface_bytes_recv_total",
                            value=float(counters.bytes_recv),
                            metric_type=MetricType.COUNTER,
                            unit="bytes",
                            tags=tags,
                        ),
                    ]
                )

        return metrics

    def _collect_load(self) -> list[DomainMetric]:
        """Collect system load metrics."""
        metrics: list[DomainMetric] = []

        try:
            load1, load5, load15 = psutil.getloadavg()
            metrics.extend(
                [
                    DomainMetric(
                        name="system.load.1min",
                        value=load1,
                        metric_type=MetricType.GAUGE,
                    ),
                    DomainMetric(
                        name="system.load.5min",
                        value=load5,
                        metric_type=MetricType.GAUGE,
                    ),
                    DomainMetric(
                        name="system.load.15min",
                        value=load15,
                        metric_type=MetricType.GAUGE,
                    ),
                ]
            )
        except (AttributeError, OSError):
            # getloadavg not available on Windows
            pass

        # Process count
        metrics.append(
            DomainMetric(
                name="system.processes.count",
                value=float(len(psutil.pids())),
                metric_type=MetricType.GAUGE,
                unit="processes",
            )
        )

        return metrics
