"""Integration: alarm engine → audit log hash-chain -- Sprint 9.

Tests the complete flow: alarm fires → AlarmEngine produces Alarm records →
AuditEvent is written to the log → hash-chain verifies OK.
"""

from __future__ import annotations

from pathlib import Path

import pytest

from arautopilot.core.alarm_engine import AlarmEngine, TelemetrySnapshot
from arautopilot.core.alarms import AlarmType, AlarmSeverity
from arautopilot.core.audit import AuditEvent, AuditLog, AuditOutcome


def _alarm_to_event(alarm) -> AuditEvent:
    return AuditEvent(
        action=f"alarm.{alarm.type}",
        outcome=AuditOutcome.FAILED,
        reason=alarm.message,
        extra={"severity": str(alarm.severity), "auto_disengage": alarm.auto_disengage_triggered},
    )


def _ack_event(alarm_type: AlarmType, user_id: str = "operator") -> AuditEvent:
    return AuditEvent(
        user_id=user_id,
        action=f"alarm.acknowledge.{alarm_type}",
        outcome=AuditOutcome.SUCCESS,
    )


class TestAlarmFiredAndLogged:
    def test_single_alarm_logged_and_chain_valid(self, tmp_path: Path):
        log = AuditLog(tmp_path / "audit.jsonl")
        engine = AlarmEngine()

        # Fire an off-course alarm
        alarms = engine.evaluate(TelemetrySnapshot(fw_alarm_off_course=True))
        assert len(alarms) == 1
        log.append(_alarm_to_event(alarms[0]))

        ok, reason = log.verify_chain()
        assert ok, reason
        assert len(log) == 1

    def test_alarm_acknowledge_logged_and_chain_valid(self, tmp_path: Path):
        log = AuditLog(tmp_path / "audit.jsonl")
        engine = AlarmEngine()

        alarms = engine.evaluate(TelemetrySnapshot(fw_alarm_off_course=True))
        log.append(_alarm_to_event(alarms[0]))

        engine.acknowledge(AlarmType.OFF_COURSE)
        log.append(_ack_event(AlarmType.OFF_COURSE))

        ok, reason = log.verify_chain()
        assert ok, reason
        assert len(log) == 2

    def test_multiple_alarms_all_logged(self, tmp_path: Path):
        log = AuditLog(tmp_path / "audit.jsonl")
        engine = AlarmEngine()

        snap = TelemetrySnapshot(
            fw_alarm_off_course=True,
            fw_alarm_voltage_low=True,
        )
        alarms = engine.evaluate(snap)
        assert len(alarms) == 2

        for a in alarms:
            log.append(_alarm_to_event(a))

        ok, reason = log.verify_chain()
        assert ok, reason
        assert len(log) == 2

    def test_disengage_event_logged_in_chain(self, tmp_path: Path):
        log = AuditLog(tmp_path / "audit.jsonl")
        disengages = []
        engine = AlarmEngine(on_disengage=lambda: disengages.append(True))

        # EMERGENCY alarm triggers auto-disengage
        alarms = engine.evaluate(TelemetrySnapshot(fw_alarm_heading_lost=True))
        assert len(disengages) >= 1
        assert alarms[0].auto_disengage_triggered

        log.append(_alarm_to_event(alarms[0]))
        log.append(AuditEvent(
            action="pilot.disengage",
            outcome=AuditOutcome.SUCCESS,
            reason="auto-disengage from alarm",
            extra={"trigger": str(alarms[0].type)},
        ))

        ok, reason = log.verify_chain()
        assert ok, reason

    def test_alarm_clear_and_refire_both_logged(self, tmp_path: Path):
        log = AuditLog(tmp_path / "audit.jsonl")
        engine = AlarmEngine()

        alarms = engine.evaluate(TelemetrySnapshot(fw_alarm_off_course=True))
        log.append(_alarm_to_event(alarms[0]))

        # Clear
        engine.evaluate(TelemetrySnapshot(fw_alarm_off_course=False))
        log.append(AuditEvent(action="alarm.cleared.off_course", outcome=AuditOutcome.SUCCESS))

        # Refire
        alarms2 = engine.evaluate(TelemetrySnapshot(fw_alarm_off_course=True))
        assert len(alarms2) == 1
        log.append(_alarm_to_event(alarms2[0]))

        ok, reason = log.verify_chain()
        assert ok, reason
        assert len(log) == 3


class TestAuditPersistenceAcrossReload:
    def test_reloaded_log_continues_chain(self, tmp_path: Path):
        p = tmp_path / "audit.jsonl"
        log1 = AuditLog(p)
        engine = AlarmEngine()
        alarms = engine.evaluate(TelemetrySnapshot(fw_alarm_off_course=True))
        log1.append(_alarm_to_event(alarms[0]))

        # Simulate restarting the Studio
        log2 = AuditLog(p)
        log2.append(AuditEvent(action="studio.startup", outcome=AuditOutcome.SUCCESS))

        ok, reason = log2.verify_chain()
        assert ok, reason
        assert len(log2) == 2

    def test_tampered_alarm_entry_detected(self, tmp_path: Path):
        import json
        p = tmp_path / "audit.jsonl"
        log = AuditLog(p)
        engine = AlarmEngine()
        alarms = engine.evaluate(TelemetrySnapshot(fw_alarm_off_course=True))
        log.append(_alarm_to_event(alarms[0]))

        # Tamper: change the action field
        lines = p.read_text(encoding="utf-8").splitlines()
        data = json.loads(lines[0])
        data["action"] = "alarm.no_problem_here"
        lines[0] = json.dumps(data)
        p.write_text("\n".join(lines) + "\n", encoding="utf-8")

        log2 = AuditLog(p)
        ok, reason = log2.verify_chain()
        assert not ok
        assert "mismatch" in reason.lower() or "tamper" in reason.lower()


class TestAlarmSeverityInAudit:
    def test_emergency_severity_recorded(self, tmp_path: Path):
        log = AuditLog(tmp_path / "audit.jsonl")
        engine = AlarmEngine()
        alarms = engine.evaluate(TelemetrySnapshot(fw_alarm_heading_lost=True))
        assert alarms[0].severity == AlarmSeverity.EMERGENCY
        event = _alarm_to_event(alarms[0])
        assert event.extra["severity"] == "emergency"
        log.append(event)
        events = log.read_all()
        assert events[0].extra["severity"] == "emergency"
