"""Core data models for sre-beacon."""

from __future__ import annotations

from dataclasses import dataclass, field
from datetime import datetime, timedelta
from enum import Enum
from typing import Optional


# ---------------------------------------------------------------------------
# Enums
# ---------------------------------------------------------------------------

class Severity(str, Enum):
    CRITICAL = "CRITICAL"
    HIGH = "HIGH"
    MEDIUM = "MEDIUM"
    LOW = "LOW"
    INFO = "INFO"


class SLIType(str, Enum):
    AVAILABILITY = "availability"
    LATENCY = "latency"
    ERROR_RATE = "error_rate"
    THROUGHPUT = "throughput"


class IncidentSeverity(str, Enum):
    P1 = "P1"
    P2 = "P2"
    P3 = "P3"
    P4 = "P4"


class ToilFrequency(str, Enum):
    HOURLY = "hourly"
    DAILY = "daily"
    WEEKLY = "weekly"
    MONTHLY = "monthly"
    QUARTERLY = "quarterly"


class MaturityLevel(str, Enum):
    INITIAL = "Initial"
    DEVELOPING = "Developing"
    DEFINED = "Defined"
    MANAGED = "Managed"
    OPTIMIZING = "Optimizing"


class AlertIssueSeverity(str, Enum):
    CRITICAL = "CRITICAL"
    HIGH = "HIGH"
    MEDIUM = "MEDIUM"
    LOW = "LOW"
    INFO = "INFO"


# ---------------------------------------------------------------------------
# SLO Models
# ---------------------------------------------------------------------------

@dataclass
class SLODefinition:
    """A Service Level Objective definition."""
    name: str
    service: str
    sli_type: SLIType
    target: float          # e.g. 99.9
    window_days: int = 30  # rolling window in days
    description: str = ""


@dataclass
class SLOResult:
    """Calculated SLO compliance result."""
    slo: SLODefinition
    actual: float                     # actual measured value
    compliant: bool = field(init=False)
    error_budget_total_minutes: float = field(init=False)
    error_budget_consumed_minutes: float = field(init=False)
    error_budget_remaining_pct: float = field(init=False)
    burn_rate: float = 0.0            # burn rate multiplier (1.0 = nominal)

    def __post_init__(self):
        if self.slo.sli_type == SLIType.ERROR_RATE:
            # For error rate: actual should be LOWER than target
            self.compliant = self.actual <= (100.0 - self.slo.target)
        else:
            # For availability/latency/throughput: actual should meet or exceed target
            self.compliant = self.actual >= self.slo.target

        total_minutes = self.slo.window_days * 24 * 60
        budget_fraction = (100.0 - self.slo.target) / 100.0
        self.error_budget_total_minutes = total_minutes * budget_fraction

        if self.slo.sli_type == SLIType.ERROR_RATE:
            consumed_fraction = self.actual / (100.0 - self.slo.target) if self.slo.target < 100 else 0
        else:
            if self.slo.target > 0:
                shortfall = max(0, self.slo.target - self.actual)
                consumed_fraction = shortfall / (100.0 - self.slo.target) if self.slo.target < 100 else 0
            else:
                consumed_fraction = 0

        self.error_budget_consumed_minutes = min(
            self.error_budget_total_minutes,
            self.error_budget_total_minutes * consumed_fraction,
        )
        self.error_budget_remaining_pct = max(
            0, 100.0 * (1.0 - consumed_fraction)
        )


# ---------------------------------------------------------------------------
# Availability Models
# ---------------------------------------------------------------------------

@dataclass
class AvailabilityInput:
    """Input for availability calculation."""
    service: str
    total_minutes: float
    downtime_minutes: float
    period: str = "30 days"


@dataclass
class AvailabilityResult:
    """Calculated availability."""
    service: str
    availability_pct: float
    nines: float               # e.g. 3.5 for 99.97%
    uptime_minutes: float
    downtime_minutes: float
    total_minutes: float
    period: str
    allowed_downtime_by_nines: dict = field(default_factory=dict)


# ---------------------------------------------------------------------------
# Incident Models
# ---------------------------------------------------------------------------

@dataclass
class Incident:
    """An incident record."""
    id: str
    title: str
    severity: IncidentSeverity
    service: str
    detected_at: datetime
    acknowledged_at: Optional[datetime] = None
    resolved_at: Optional[datetime] = None
    root_cause: str = ""
    action_items: list = field(default_factory=list)

    @property
    def ttd(self) -> Optional[timedelta]:
        """Time to detect (as duration — 0 if detected_at is the start)."""
        return None

    @property
    def tta(self) -> Optional[timedelta]:
        """Time to acknowledge."""
        if self.acknowledged_at and self.detected_at:
            return self.acknowledged_at - self.detected_at
        return None

    @property
    def ttr(self) -> Optional[timedelta]:
        """Time to resolve."""
        if self.resolved_at and self.detected_at:
            return self.resolved_at - self.detected_at
        return None


@dataclass
class IncidentMetrics:
    """Aggregated incident metrics."""
    total_incidents: int
    mttr: Optional[timedelta]
    mtta: Optional[timedelta]
    mtbf: Optional[timedelta]
    by_severity: dict = field(default_factory=dict)
    by_service: dict = field(default_factory=dict)
    incidents_with_action_items_pct: float = 0.0
    p1_p2_count: int = 0


# ---------------------------------------------------------------------------
# Toil Models
# ---------------------------------------------------------------------------

@dataclass
class ToilTask:
    """A toil task description."""
    name: str
    frequency: ToilFrequency
    duration_minutes: int
    automatable: bool = True
    category: str = "general"
    description: str = ""


@dataclass
class ToilReport:
    """Aggregated toil analysis."""
    tasks: list
    total_toil_hours_per_month: float
    toil_percentage: float              # of total engineering hours
    automatable_hours_per_month: float
    automation_potential_pct: float
    by_category: dict = field(default_factory=dict)
    by_frequency: dict = field(default_factory=dict)
    engineering_hours_per_month: float = 160.0  # default per-person


# ---------------------------------------------------------------------------
# Alert Rule Models
# ---------------------------------------------------------------------------

@dataclass
class AlertRule:
    """A Prometheus / alerting rule."""
    name: str
    expr: str
    for_duration: str = ""
    severity: str = ""
    labels: dict = field(default_factory=dict)
    annotations: dict = field(default_factory=dict)
    file_path: str = ""
    group: str = ""


@dataclass
class AlertIssue:
    """An issue found in an alert rule."""
    rule_id: str
    severity: AlertIssueSeverity
    message: str
    alert_name: str
    file_path: str = ""
    suggestion: str = ""


@dataclass
class AlertAuditReport:
    """Results of alert rule analysis."""
    total_rules: int
    issues: list = field(default_factory=list)
    rules_without_runbook: int = 0
    rules_without_severity: int = 0
    rules_without_for: int = 0
    duplicate_names: list = field(default_factory=list)

    @property
    def critical_count(self) -> int:
        return sum(1 for i in self.issues if i.severity == AlertIssueSeverity.CRITICAL)

    @property
    def high_count(self) -> int:
        return sum(1 for i in self.issues if i.severity == AlertIssueSeverity.HIGH)

    @property
    def medium_count(self) -> int:
        return sum(1 for i in self.issues if i.severity == AlertIssueSeverity.MEDIUM)

    @property
    def low_count(self) -> int:
        return sum(1 for i in self.issues if i.severity == AlertIssueSeverity.LOW)

    @property
    def info_count(self) -> int:
        return sum(1 for i in self.issues if i.severity == AlertIssueSeverity.INFO)

    @property
    def score(self) -> int:
        if self.total_rules == 0:
            return 100
        max_deductions = self.total_rules * 10
        deductions = (
            self.critical_count * 10 +
            self.high_count * 7 +
            self.medium_count * 4 +
            self.low_count * 2 +
            self.info_count * 1
        )
        return max(0, int(100 - (deductions / max(1, max_deductions)) * 100))

    @property
    def grade(self) -> str:
        s = self.score
        if s >= 95:
            return "A+"
        if s >= 90:
            return "A"
        if s >= 80:
            return "B"
        if s >= 70:
            return "C"
        if s >= 60:
            return "D"
        return "F"


# ---------------------------------------------------------------------------
# Maturity Models
# ---------------------------------------------------------------------------

@dataclass
class MaturityDimension:
    """A single maturity dimension score."""
    name: str
    score: float            # 0-5
    level: MaturityLevel = field(init=False)
    findings: list = field(default_factory=list)
    recommendations: list = field(default_factory=list)

    def __post_init__(self):
        self._set_level()

    def _set_level(self):
        if self.score < 1:
            self.level = MaturityLevel.INITIAL
        elif self.score < 2:
            self.level = MaturityLevel.DEVELOPING
        elif self.score < 3:
            self.level = MaturityLevel.DEFINED
        elif self.score < 4:
            self.level = MaturityLevel.MANAGED
        else:
            self.level = MaturityLevel.OPTIMIZING


@dataclass
class MaturityReport:
    """SRE maturity assessment report."""
    dimensions: list = field(default_factory=list)

    @property
    def overall_score(self) -> float:
        if not self.dimensions:
            return 0.0
        return sum(d.score for d in self.dimensions) / len(self.dimensions)

    @property
    def overall_level(self) -> MaturityLevel:
        s = self.overall_score
        if s < 1:
            return MaturityLevel.INITIAL
        if s < 2:
            return MaturityLevel.DEVELOPING
        if s < 3:
            return MaturityLevel.DEFINED
        if s < 4:
            return MaturityLevel.MANAGED
        return MaturityLevel.OPTIMIZING

    @property
    def weakest_dimension(self) -> Optional[MaturityDimension]:
        if not self.dimensions:
            return None
        return min(self.dimensions, key=lambda d: d.score)

    @property
    def strongest_dimension(self) -> Optional[MaturityDimension]:
        if not self.dimensions:
            return None
        return max(self.dimensions, key=lambda d: d.score)
