"""Pydantic models shared across modules."""

from __future__ import annotations

from datetime import datetime, timezone
from enum import Enum
from pathlib import Path
from typing import Any, Dict, List, Optional

from pydantic import BaseModel, Field, field_validator


class AzureEnvironment(str, Enum):
    """Supported Azure clouds."""

    AZURE_PUBLIC = "azure_public"
    AZURE_GOV = "azure_gov"
    AZURE_CHINA = "azure_china"
    AZURE_GERMANY = "azure_germany"
    AZURE_STACK = "azure_stack"


class AzureEnvironmentConfig(BaseModel):
    """Concrete endpoints for an Azure cloud."""

    name: AzureEnvironment
    authority_host: str
    resource_manager: str
    graph_endpoint: str
    storage_suffix: str


class DiscoveryFilter(BaseModel):
    """Optional filters used to scope discovery."""

    include_types: Optional[List[str]] = None
    exclude_types: Optional[List[str]] = None
    required_tags: Optional[Dict[str, str]] = None
    resource_groups: Optional[List[str]] = None


class VisualizationOptions(BaseModel):
    """Graph visualization configuration."""

    output_dir: Path = Field(default=Path("artifacts/graphs"))
    file_name: Optional[str] = None
    include_attributes: bool = True
    physics_enabled: bool = True

    @field_validator("output_dir", mode="before")
    @classmethod
    def _ensure_path(cls, value: Any) -> Path:
        if value is None:
            raise ValueError("output_dir cannot be None")
        return Path(value)


class ResourceNode(BaseModel):
    """Normalized node in the discovery graph."""

    id: str
    name: str
    type: str
    subscription_id: str

    location: Optional[str] = None
    resource_group: Optional[str] = None

    tags: Dict[str, Any] = Field(default_factory=dict)
    properties: Dict[str, Any] = Field(default_factory=dict)
    dependencies: List[str] = Field(default_factory=list)


class ResourceRelationship(BaseModel):
    """Directed edge between two nodes."""

    source_id: str
    target_id: str
    relation_type: str
    weight: float = 1.0


class VisualizationResponse(BaseModel):
    """Output of rendering a graph visualization."""

    html_path: str
    nodes: int
    relationships: int


class DiscoveryPhaseMetrics(BaseModel):
    """Per-phase runtime stats for the orchestrator."""

    name: str
    elapsed_seconds: float
    nodes_added: int = 0
    relationships_added: int = 0


class DiscoveryMergeMetrics(BaseModel):
    """Relationship merge diagnostics."""

    dropped_dangling_relationships: int = 0
    deduped_relationships: int = 0


class DiscoveryMetrics(BaseModel):
    """Optional metrics payload included when requested."""

    phases: List[DiscoveryPhaseMetrics] = Field(default_factory=list)
    merge: DiscoveryMergeMetrics = Field(default_factory=DiscoveryMergeMetrics)


class AzureDiscoveryResponse(BaseModel):
    """Discovery output payload."""

    tenant_id: str
    discovered_subscriptions: List[str] = Field(default_factory=list)

    nodes: List[ResourceNode] = Field(default_factory=list)
    relationships: List[ResourceRelationship] = Field(default_factory=list)

    total_resources: int = 0
    html_report_path: Optional[str] = None

    metrics: Optional[DiscoveryMetrics] = None
    generated_at: datetime = Field(default_factory=lambda: datetime.now(timezone.utc))


class ScaleControlConfig(BaseModel):
    """Advanced scaling and throttling configuration."""

    enabled: bool = True
    initial_rps: float = Field(default=10.0, ge=0.1, le=100.0)
    min_rps: float = Field(default=1.0, ge=0.1, le=100.0)
    max_rps: float = Field(default=50.0, ge=1.0, le=200.0)
    max_concurrent_batches: int = Field(default=5, ge=1, le=20)
    adaptive_batching: bool = True
    initial_batch_size: int = Field(default=1000, ge=10, le=10000)


class DefenderConfig(BaseModel):
    """Defender for Cloud enumeration configuration."""

    include_security_alerts: bool = True
    include_security_assessments: bool = True
    include_secure_scores: bool = True
    include_compliance_status: bool = False  # Not yet implemented

    # Alert filtering
    alert_severity_filter: Optional[List[str]] = None  # ["High", "Medium", "Low", "Informational"]
    alert_status_filter: Optional[List[str]] = None  # ["Active", "Resolved", "Dismissed"]

    # Assessment filtering
    assessment_severity_filter: Optional[List[str]] = None  # ["High", "Medium", "Low"]
    assessment_status_filter: Optional[List[str]] = None  # ["Healthy", "Unhealthy", "NotApplicable"]


class PIMConfig(BaseModel):
    """PIM (Privileged Identity Management) enumeration configuration."""

    # Entra ID role eligibilities
    include_entra_role_eligibilities: bool = True
    include_entra_role_eligibility_requests: bool = False  # Pending/active requests

    # Azure Resource role eligibilities
    include_azure_resource_eligibilities: bool = True


class AzureDiscoveryRequest(BaseModel):
    """Input payload for discovery operations."""

    tenant_id: str

    environment: AzureEnvironment = AzureEnvironment.AZURE_PUBLIC
    detect_environment: bool = False
    detect_timeout_seconds: float = Field(default=8.0, ge=1.0, le=60.0)

    subscriptions: Optional[List[str]] = None
    filter: Optional[DiscoveryFilter] = None

    include_entra: bool = False
    include_m365: bool = False
    include_purview: bool = False
    include_defender_cloud: bool = False

    # RBAC enumeration controls
    include_rbac_assignments: bool = False
    include_rbac_definitions: bool = False
    rbac_scope_filter: Optional[List[str]] = None

    # PIM enumeration controls
    include_pim: bool = False
    pim_scope_filter: Optional[List[str]] = None

    include_metrics: bool = False
    materialize_missing_endpoints: bool = False

    # ARM/ResourceGraph scoping controls
    arm_max_objects: int = Field(default=0, ge=0, le=1000000)

    # Global Microsoft Graph scoping controls
    graph_total_max_objects: int = Field(default=0, ge=0, le=1000000)

    # Entra scoping controls
    entra_include_organization: bool = True
    entra_include_domains: bool = True

    entra_include_users: bool = True
    entra_include_groups: bool = True
    entra_include_applications: bool = True
    entra_include_conditional_access_policies: bool = True
    entra_include_risky_users: bool = True
    entra_max_objects: int = Field(default=0, ge=0, le=500000)

    # Relationship scoping controls (Graph)
    entra_group_membership_max_groups: int = Field(default=50, ge=0, le=5000)
    entra_group_membership_max_members_per_group: int = Field(default=200, ge=0, le=500000)

    entra_ownership_max_apps: int = Field(default=50, ge=0, le=5000)
    entra_ownership_max_owners_per_app: int = Field(default=50, ge=0, le=500000)

    entra_sp_ownership_max_sps: int = Field(default=50, ge=0, le=5000)
    entra_sp_ownership_max_owners_per_sp: int = Field(default=50, ge=0, le=500000)

    # M365 scoping controls
    m365_include_applications: bool = True
    m365_include_service_principals: bool = True
    m365_max_objects: int = Field(default=0, ge=0, le=500000)

    include_relationships: bool = True
    max_batch_size: int = Field(default=1000, ge=100, le=5000)
    throttle_delay_seconds: float = Field(default=0.05, ge=0.0, le=5.0)
    prefer_cli_credentials: bool = False

    visualization: VisualizationOptions = Field(default_factory=VisualizationOptions)

    # Scale control configuration
    scale_controls: ScaleControlConfig = Field(default_factory=ScaleControlConfig)

    # Defender for Cloud configuration
    defender_config: DefenderConfig = Field(default_factory=DefenderConfig)

    # PIM configuration
    pim_config: PIMConfig = Field(default_factory=PIMConfig)

    @field_validator("tenant_id")
    @classmethod
    def _validate_tenant(cls, value: str) -> str:
        if not value or not value.strip():
            raise ValueError("tenant_id is required")
        return value.strip()


# Alias for backwards compatibility
Relationship = ResourceRelationship
