"""Domain models for scans, graph correlation, and reports."""

from __future__ import annotations

from datetime import UTC, datetime
from typing import Any, Literal

from pydantic import BaseModel, Field


def utc_now() -> datetime:
    return datetime.now(UTC)


class Finding(BaseModel):
    title: str
    severity: Literal["low", "medium", "high", "critical"] = "medium"
    confidence: float = Field(default=0.5, ge=0.0, le=1.0)
    description: str
    tags: list[str] = Field(default_factory=list)


class RawEvidence(BaseModel):
    source: str
    kind: str
    value: str
    confidence: float = Field(default=0.0, ge=0.0, le=1.0)
    attributes: dict[str, Any] = Field(default_factory=dict)


class ObservedFact(BaseModel):
    field: str
    value: str
    source: str
    confidence: float = Field(default=0.0, ge=0.0, le=1.0)
    attributes: dict[str, Any] = Field(default_factory=dict)


class InferenceHint(BaseModel):
    field: str
    value: str
    source: str
    rationale: str
    confidence: float = Field(default=0.0, ge=0.0, le=1.0)
    supporting_values: list[str] = Field(default_factory=list)
    attributes: dict[str, Any] = Field(default_factory=dict)


class DiscoveryProviderRecord(BaseModel):
    name: str
    kind: Literal["active", "passive", "analysis"] = "active"
    protocol: str
    confidence: float = Field(default=0.0, ge=0.0, le=1.0)
    metadata: dict[str, Any] = Field(default_factory=dict)


class PassiveObservation(BaseModel):
    provider: str
    protocol: str
    summary: str
    source_address: str | None = None
    confidence: float = Field(default=0.0, ge=0.0, le=1.0)
    attributes: dict[str, Any] = Field(default_factory=dict)


class Service(BaseModel):
    port: int = Field(ge=1, le=65535)
    protocol: str
    service: str
    banner: str | None = None
    confidence: float = Field(default=0.5, ge=0.0, le=1.0)
    fingerprint_source: str = "port-map"
    product: str | None = None
    normalized_product: str | None = None
    version: str | None = None
    metadata: dict[str, Any] = Field(default_factory=dict)
    evidence: list[RawEvidence] = Field(default_factory=list)
    observed_facts: list[ObservedFact] = Field(default_factory=list)
    inference_hints: list[InferenceHint] = Field(default_factory=list)


class CertificateMetadata(BaseModel):
    subject: str | None = None
    issuer: str | None = None
    common_name: str | None = None
    san: list[str] = Field(default_factory=list)
    fingerprint_sha256: str | None = None
    confidence: float = Field(default=0.0, ge=0.0, le=1.0)


class Endpoint(BaseModel):
    url: str
    kind: str = "endpoint"
    confidence: float = Field(default=0.0, ge=0.0, le=1.0)


class SoftwareComponent(BaseModel):
    name: str
    normalized_name: str
    version: str | None = None
    source: str = "inferred"
    confidence: float = Field(default=0.0, ge=0.0, le=1.0)


class ExtractedArtifact(BaseModel):
    relative_path: str
    source: str
    kind: str = "file"
    size_bytes: int = Field(default=0, ge=0)
    sha256: str | None = None
    media_type: str | None = None
    extracted_from: str | None = None
    depth: int = Field(default=0, ge=0)
    confidence: float = Field(default=0.0, ge=0.0, le=1.0)


class ConfigHint(BaseModel):
    path: str
    key: str
    value_preview: str
    category: str = "config"
    confidence: float = Field(default=0.0, ge=0.0, le=1.0)
    inferred: bool = False


class ServiceHint(BaseModel):
    service: str
    source_path: str
    rationale: str
    source_kind: str = "content-token"
    confidence: float = Field(default=0.0, ge=0.0, le=1.0)
    inferred: bool = True


class ComponentEvidence(BaseModel):
    component: SoftwareComponent
    source_path: str
    evidence_type: str
    detail: str
    confidence: float = Field(default=0.0, ge=0.0, le=1.0)


class ExtractionMetadata(BaseModel):
    provider: str = ""
    detected_type: str = "unknown"
    extracted_root: str = ""
    member_count: int = Field(default=0, ge=0)
    total_bytes: int = Field(default=0, ge=0)
    max_files: int = Field(default=0, ge=0)
    max_total_bytes: int = Field(default=0, ge=0)
    truncated: bool = False
    depth_limit_hit: bool = False
    nested_archives_seen: int = Field(default=0, ge=0)
    extraction_notes: list[str] = Field(default_factory=list)


class CertificateFinding(BaseModel):
    path: str
    kind: str = "certificate"
    subject: str | None = None
    issuer: str | None = None
    common_name: str | None = None
    san: list[str] = Field(default_factory=list)
    fingerprint_sha256: str | None = None
    confidence: float = Field(default=0.0, ge=0.0, le=1.0)
    inferred: bool = False
    rationale: str = ""


class VulnerabilityMatch(BaseModel):
    cve_id: str
    vendor: str | None = None
    product: str
    normalized_product: str
    category: Literal["confirmed-match", "probable-match", "weak-hint"] = "probable-match"
    severity: str = "medium"
    summary: str
    fixed_in: str | None = None
    source: str = "offline-feed"
    confidence: float = Field(default=0.0, ge=0.0, le=1.0)
    inference: bool = False
    matched_on: list[str] = Field(default_factory=list)
    evidence_refs: list[str] = Field(default_factory=list)
    rationale: str = ""


class Device(BaseModel):
    host: str
    ip_address: str
    mac_address: str | None = None
    vendor: str | None = None
    model: str | None = None
    fingerprint_confidence: float = Field(default=0.0, ge=0.0, le=1.0)
    fingerprint_matches: list[str] = Field(default_factory=list)
    fingerprint_evidence: list[RawEvidence] = Field(default_factory=list)
    protocols: list[str] = Field(default_factory=list)
    services: list[Service] = Field(default_factory=list)
    software_components: list[SoftwareComponent] = Field(default_factory=list)
    certificates: list[CertificateMetadata] = Field(default_factory=list)
    endpoints: list[Endpoint] = Field(default_factory=list)
    vulnerability_matches: list[VulnerabilityMatch] = Field(default_factory=list)
    findings: list[Finding] = Field(default_factory=list)


class FirmwareArtifact(BaseModel):
    path: str
    sha256: str
    size_bytes: int = Field(ge=0)
    extracted_strings: list[str] = Field(default_factory=list)
    suspected_vendors: list[str] = Field(default_factory=list)
    suspected_products: list[str] = Field(default_factory=list)
    software_components: list[SoftwareComponent] = Field(default_factory=list)
    component_evidence: list[ComponentEvidence] = Field(default_factory=list)
    extracted_artifacts: list[ExtractedArtifact] = Field(default_factory=list)
    extracted_metadata: dict[str, Any] = Field(default_factory=dict)
    extraction_metadata: ExtractionMetadata = Field(default_factory=ExtractionMetadata)
    certificates: list[CertificateMetadata] = Field(default_factory=list)
    certificate_findings: list[CertificateFinding] = Field(default_factory=list)
    config_hints: list[ConfigHint] = Field(default_factory=list)
    service_hints: list[ServiceHint] = Field(default_factory=list)
    observed_facts: list[ObservedFact] = Field(default_factory=list)
    inference_hints: list[InferenceHint] = Field(default_factory=list)
    vulnerability_matches: list[VulnerabilityMatch] = Field(default_factory=list)
    findings: list[Finding] = Field(default_factory=list)


class EvidenceNode(BaseModel):
    id: str
    kind: str
    label: str
    attributes: dict[str, Any] = Field(default_factory=dict)


class EvidenceEdge(BaseModel):
    source: str
    target: str
    relation: str
    attributes: dict[str, Any] = Field(default_factory=dict)


class NetworkScanArtifact(BaseModel):
    target: str
    created_at: datetime = Field(default_factory=utc_now)
    devices: list[Device] = Field(default_factory=list)
    resumed: bool = False
    cache_hits: int = 0
    discovery_providers: list[DiscoveryProviderRecord] = Field(default_factory=list)
    passive_observations: list[PassiveObservation] = Field(default_factory=list)


class PassiveScanArtifact(BaseModel):
    created_at: datetime = Field(default_factory=utc_now)
    target: str = "passive-listen"
    observations: list[PassiveObservation] = Field(default_factory=list)
    discovery_providers: list[DiscoveryProviderRecord] = Field(default_factory=list)
    duration_seconds: float = Field(default=0.0, ge=0.0)
    source: str = "summary-parser"


class CorrelationArtifact(BaseModel):
    created_at: datetime = Field(default_factory=utc_now)
    nodes: list[EvidenceNode] = Field(default_factory=list)
    edges: list[EvidenceEdge] = Field(default_factory=list)
    summary: dict[str, int] = Field(default_factory=dict)
    insights: dict[str, Any] = Field(default_factory=dict)


class ReportArtifact(BaseModel):
    created_at: datetime = Field(default_factory=utc_now)
    format: Literal["terminal", "markdown", "json", "html"] = "terminal"
    title: str = "Sentra Evidence Report"
    summary: dict[str, int] = Field(default_factory=dict)
    highlights: list[str] = Field(default_factory=list)
    devices: list[dict[str, Any]] = Field(default_factory=list)
    risks: list[dict[str, Any]] = Field(default_factory=list)
    top_risky_devices: list[dict[str, Any]] = Field(default_factory=list)
    grouped_findings: list[dict[str, Any]] = Field(default_factory=list)
    weak_paths: list[dict[str, Any]] = Field(default_factory=list)
    recommended_checks: list[str] = Field(default_factory=list)
    firmware_analysis: list[dict[str, Any]] = Field(default_factory=list)
    graph_insights: dict[str, Any] = Field(default_factory=dict)
    intelligence_summary: dict[str, Any] = Field(default_factory=dict)


class WorkspaceArtifactRecord(BaseModel):
    name: str
    path: str
    present: bool = False
    size_bytes: int = Field(default=0, ge=0)
    modified_at: datetime | None = None


class WorkspaceInspection(BaseModel):
    workspace: str
    state_dir: str
    artifact_count: int = Field(default=0, ge=0)
    firmware_extract_dirs: int = Field(default=0, ge=0)
    firmware_extract_files: int = Field(default=0, ge=0)
    artifacts: list[WorkspaceArtifactRecord] = Field(default_factory=list)


class CapabilityRecord(BaseModel):
    name: str
    category: str
    summary: str
    available: bool = True


class StatusArtifact(BaseModel):
    workspace: str
    version: str
    artifact_count: int = Field(default=0, ge=0)
    device_count: int = Field(default=0, ge=0)
    finding_count: int = Field(default=0, ge=0)
    graph_nodes: int = Field(default=0, ge=0)
    graph_edges: int = Field(default=0, ge=0)
    firmware_extracted_files: int = Field(default=0, ge=0)
    firmware_certificates: int = Field(default=0, ge=0)
    firmware_certificate_findings: int = Field(default=0, ge=0)
    firmware_config_hints: int = Field(default=0, ge=0)
    firmware_service_hints: int = Field(default=0, ge=0)
    firmware_components: int = Field(default=0, ge=0)
    firmware_inference_hints: int = Field(default=0, ge=0)
    firmware_extraction_provider: str = ""
    capabilities: list[CapabilityRecord] = Field(default_factory=list)
    artifacts_present: list[str] = Field(default_factory=list)


# Backward-compatible aliases for existing modules.
EvidenceFinding = Finding
ServiceEvidence = Service
DeviceEvidence = Device
FirmwareEvidence = FirmwareArtifact

