"""
Compliance models — Wazuh SCA (Security Configuration Assessment) results.

Wazuh evaluates each agent against one or more SCA policies (CIS
benchmarks, vendor hardening guides) and reports per-check pass/fail/
not-applicable. We persist two layers:

- ComplianceResult: per (asset, policy) snapshot — totals + score
- ComplianceCheck: per individual check — raw outcome + remediation

ComplianceCheck is optional (heavy detail) and only stored when the
admin asks for the deep-dive view; the per-policy summary is what the
asset detail + dashboard widgets read by default.
"""
from datetime import datetime
from sqlalchemy import (
    Column, Integer, String, DateTime, Float, ForeignKey, Text, Index,
    UniqueConstraint,
)
from sqlalchemy.orm import relationship, backref

from app.models.base import Base, TimestampMixin


class ComplianceResult(Base, TimestampMixin):
    """One row per (asset, policy_id) snapshot — overwritten on refresh."""
    __tablename__ = "compliance_results"

    id = Column(Integer, primary_key=True)
    asset_id = Column(Integer, ForeignKey("assets.id", ondelete="CASCADE"),
                      nullable=False, index=True)

    # Wazuh-side identifiers — policy_id is the SCA policy slug
    # ("cis_win11", "cis_ubuntu22_04", "wazuh_default_policy" etc.).
    policy_id = Column(String(64), nullable=False, index=True)
    policy_name = Column(String(255), nullable=True)
    policy_description = Column(Text, nullable=True)

    # Tally
    total_checks = Column(Integer, nullable=False, default=0)
    pass_count = Column(Integer, nullable=False, default=0)
    fail_count = Column(Integer, nullable=False, default=0)
    not_applicable_count = Column(Integer, nullable=False, default=0)

    # score = pass / (pass + fail) × 100. Stored explicitly to avoid
    # division in every query / sort. Null when (pass+fail) == 0.
    score = Column(Float, nullable=True, index=True)
    # weighted_score uses per-check impact (0-100) from compliance_impacts
    # mapping table when available. Falls back to None when no impacts
    # are loaded for the policy. Range 0-100.
    weighted_score = Column(Float, nullable=True, index=True)

    # When Wazuh last evaluated this policy (from the SCA endpoint)
    end_scan = Column(DateTime, nullable=True)
    # When VulnCheck last refreshed from Wazuh
    last_synced = Column(DateTime, nullable=False, default=datetime.utcnow)

    # ORM. cascade + passive_deletes so DELETE on Asset propagates via
    # the DB-side ON DELETE CASCADE without SQLAlchemy first issuing an
    # autoflush UPDATE asset_id=NULL (which trips NOT NULL).
    asset = relationship(
        "Asset",
        backref=backref(
            "compliance_results",
            cascade="all, delete-orphan",
            passive_deletes=True,
        ),
    )

    __table_args__ = (
        UniqueConstraint("asset_id", "policy_id", name="uq_compliance_asset_policy"),
        Index("ix_compliance_score_desc", "score"),
    )

    def recalc_score(self) -> None:
        denom = (self.pass_count or 0) + (self.fail_count or 0)
        self.score = round(100.0 * (self.pass_count or 0) / denom, 2) if denom else None


class ComplianceCheck(Base, TimestampMixin):
    """Per-check raw outcome. Optional deep-dive; not populated by the
    default refresh. Endpoint `/compliance/{asset_id}/{policy_id}/checks`
    can pull on demand."""
    __tablename__ = "compliance_checks"

    id = Column(Integer, primary_key=True)
    result_id = Column(Integer, ForeignKey("compliance_results.id", ondelete="CASCADE"),
                       nullable=False, index=True)

    check_id = Column(String(64), nullable=False)  # Wazuh internal check id
    title = Column(String(500), nullable=True)
    description = Column(Text, nullable=True)
    rationale = Column(Text, nullable=True)
    remediation = Column(Text, nullable=True)
    result = Column(String(20), nullable=False, index=True)  # passed | failed | not applicable
    severity = Column(String(20), nullable=True)             # low | medium | high (Wazuh side)

    result_obj = relationship(
        "ComplianceResult",
        backref=backref(
            "checks",
            cascade="all, delete-orphan",
            passive_deletes=True,
        ),
    )

    __table_args__ = (
        Index("ix_compliance_check_result_result", "result_id", "result"),
    )


class ComplianceImpact(Base, TimestampMixin):
    """
    Per-check impact rating used to weight SCA results.

    Loaded via CSV import (CIS-Benchmark Workbench export or manual).
    A given check_id can belong to multiple benchmarks (Win11 Level 1
    + Win11 Level 2 + Win11+BitLocker) — the benchmark column scopes
    the impact so we can keep distinct values per profile.

    Lookup order during scoring:
      1) (cis_id, benchmark) exact match
      2) (cis_id, ANY benchmark) → first hit  (CIS-Workbench cross-OS)
      3) → fallback impact = 50 (neutral)
    """
    __tablename__ = "compliance_impacts"

    id = Column(Integer, primary_key=True)
    # CIS subsection id like "2.3.4.1" — matches Wazuh check `cis` tag.
    cis_id = Column(String(64), nullable=False, index=True)
    # Free-form benchmark label parsed from the CSV filename or column.
    # Examples: "windows_server_2025_level1_member_server",
    #           "windows11_level2_bitlocker_cis".
    benchmark = Column(String(128), nullable=False, index=True)
    level = Column(String(16), nullable=True)   # "L1" | "L2" | "NG" | …
    title = Column(String(500), nullable=True)
    impact = Column(Integer, nullable=False, default=50)  # 0-100

    __table_args__ = (
        UniqueConstraint("cis_id", "benchmark",
                         name="uq_compliance_impact_cis_benchmark"),
        Index("ix_compliance_impacts_benchmark_cis", "benchmark", "cis_id"),
    )


class AssetRiskSnapshot(Base, TimestampMixin):
    """
    Daily URS snapshot per asset — feeds the trend arrow on dashboard +
    asset detail (improving / stable / degrading vs last week).

    One row per (asset_id, snapshot_date). Older rows pruned by a
    scheduler job (kept ~90 days by default).
    """
    __tablename__ = "asset_risk_snapshots"

    id = Column(Integer, primary_key=True)
    asset_id = Column(Integer, ForeignKey("assets.id", ondelete="CASCADE"),
                      nullable=False, index=True)
    snapshot_date = Column(DateTime, nullable=False, default=datetime.utcnow,
                           index=True)
    avs = Column(Float, nullable=True)   # Asset Vulnerability Score 0-100
    ass = Column(Float, nullable=True)   # Asset Security Score      0-100
    urs = Column(Float, nullable=True)   # Unified Risk Score        0-100
    severity = Column(String(16), nullable=True)  # CRITICAL|HIGH|MEDIUM|LOW|NONE

    asset = relationship(
        "Asset",
        backref=backref(
            "risk_snapshots",
            cascade="all, delete-orphan",
            passive_deletes=True,
        ),
    )

    __table_args__ = (
        UniqueConstraint("asset_id", "snapshot_date",
                         name="uq_asset_risk_per_day"),
    )

