"""Passive discovery provider abstractions for future lab-safe listeners."""

from __future__ import annotations

from dataclasses import dataclass, field
from pathlib import Path
import re
from typing import Protocol

from sentra.models import DiscoveryProviderRecord, PassiveObservation


@dataclass(frozen=True)
class PassiveDiscoveryRequest:
    duration_seconds: float = 5.0
    max_events: int = 128
    protocols: tuple[str, ...] = ("mdns", "ssdp")


@dataclass(frozen=True)
class PassiveDiscoveryResult:
    observations: list[PassiveObservation] = field(default_factory=list)
    providers: list[DiscoveryProviderRecord] = field(default_factory=list)


class PassiveDiscoveryProvider(Protocol):
    name: str

    def listen(self, request: PassiveDiscoveryRequest) -> PassiveDiscoveryResult:
        """Observe network summaries without injecting traffic."""


class PassiveMetadataParser(Protocol):
    name: str

    def parse_summary(self, summary: str, *, source: str | None = None) -> list[PassiveObservation]:
        """Convert a text summary into structured passive observations."""


class LineSummaryParser:
    name = "line-summary-parser"

    def parse_summary(self, summary: str, *, source: str | None = None) -> list[PassiveObservation]:
        observations: list[PassiveObservation] = []
        for line in summary.splitlines():
            normalized = line.strip()
            if not normalized:
                continue
            lowered = normalized.lower()
            protocol = _detect_protocol(lowered)
            if protocol is None:
                continue
            source_address = _extract_source_address(normalized)
            observations.append(
                PassiveObservation(
                    provider=self.name,
                    protocol=protocol,
                    summary=normalized[:200],
                    source_address=source_address or source,
                    confidence=_protocol_confidence(protocol),
                    attributes={"parsed_from": "summary-line"},
                )
            )
        observations.sort(key=lambda item: (item.protocol, item.provider, item.summary, item.source_address or ""))
        return observations


class SummaryFilePassiveProvider:
    name = "summary-file"

    def __init__(self, summary_file: Path, parser: PassiveMetadataParser | None = None) -> None:
        self.summary_file = Path(summary_file)
        self.parser = parser or LineSummaryParser()

    def listen(self, request: PassiveDiscoveryRequest) -> PassiveDiscoveryResult:
        content = self.summary_file.read_text(encoding="utf-8")
        observations = self.parser.parse_summary(content, source=str(self.summary_file))
        filtered = [item for item in observations if item.protocol in request.protocols][: request.max_events]
        return PassiveDiscoveryResult(
            observations=filtered,
            providers=[
                DiscoveryProviderRecord(
                    name=self.name,
                    kind="passive",
                    protocol="summary",
                    confidence=0.8,
                    metadata={"path": str(self.summary_file), "observation_count": len(filtered)},
                )
            ],
        )


def collect_passive_observations(
    providers: list[PassiveDiscoveryProvider],
    request: PassiveDiscoveryRequest,
) -> PassiveDiscoveryResult:
    observations: list[PassiveObservation] = []
    provider_records: list[DiscoveryProviderRecord] = []
    for provider in providers:
        result = provider.listen(request)
        observations.extend(result.observations)
        provider_records.extend(result.providers)
    observations.sort(key=lambda item: (item.protocol, item.provider, item.summary, item.source_address or ""))
    provider_records.sort(key=lambda item: (item.kind, item.protocol, item.name))
    return PassiveDiscoveryResult(observations=observations, providers=provider_records)


def _detect_protocol(line: str) -> str | None:
    if "mdns" in line or "_tcp.local" in line or "_udp.local" in line:
        return "mdns"
    if "ssdp" in line or "upnp" in line:
        return "ssdp"
    if "dhcp" in line or "vendor class" in line or "option 55" in line:
        return "dhcp"
    if "snmp" in line or "sysdescr" in line:
        return "snmp"
    return None


def _extract_source_address(line: str) -> str | None:
    match = re.search(r"\b\d{1,3}(?:\.\d{1,3}){3}\b", line)
    return match.group(0) if match else None


def _protocol_confidence(protocol: str) -> float:
    return {"mdns": 0.78, "ssdp": 0.76, "dhcp": 0.68, "snmp": 0.72}.get(protocol, 0.6)

