"""ExportTracker unit tests — direct testing of status reporting, timing, and error handling."""

from __future__ import annotations

import time
from unittest.mock import MagicMock

from matrice_export.tracking import ExportTracker

from .conftest import MockActionTracker

# ------------------------------------------------------------------ #
# 1. No-op when action_tracker is None
# ------------------------------------------------------------------ #


class TestExportTrackerNoTracker:
    def test_pipeline_start_noop(self):
        """report_pipeline_start is a no-op when action_tracker is None."""
        tracker = ExportTracker(action_tracker=None)
        tracker.report_pipeline_start(["onnx", "torchscript"])  # should not raise

    def test_pipeline_complete_noop(self):
        """report_pipeline_complete is a no-op when action_tracker is None."""
        tracker = ExportTracker(action_tracker=None)
        tracker.report_pipeline_complete({"onnx": {"status": "success"}})

    def test_export_start_noop(self):
        """report_export_start is a no-op when action_tracker is None."""
        tracker = ExportTracker(action_tracker=None)
        tracker.report_export_start("onnx")

    def test_export_success_noop(self):
        """report_export_success is a no-op when action_tracker is None."""
        tracker = ExportTracker(action_tracker=None)
        tracker.report_export_success("onnx", "/tmp/model.onnx")

    def test_export_failure_noop(self):
        """report_export_failure is a no-op when action_tracker is None."""
        tracker = ExportTracker(action_tracker=None)
        tracker.report_export_failure("onnx", "some error")


# ------------------------------------------------------------------ #
# 2. Step codes with MockActionTracker
# ------------------------------------------------------------------ #


class TestExportTrackerStepCodes:
    def test_pipeline_start_sends_ack_and_start(self):
        """report_pipeline_start sends MDL_EXP_ACK then MDL_EXP_STR."""
        at = MockActionTracker()
        tracker = ExportTracker(action_tracker=at)
        tracker.report_pipeline_start(["onnx"])

        codes = [c[0] for c in at.calls]
        assert codes == ["MDL_EXP_ACK", "MDL_EXP_STR"]

    def test_pipeline_complete_sends_cmpl(self):
        """report_pipeline_complete sends MDL_EXP_CMPL."""
        at = MockActionTracker()
        tracker = ExportTracker(action_tracker=at)
        tracker.report_pipeline_start(["onnx"])
        tracker.report_pipeline_complete({"onnx": {"status": "success"}})

        codes = [c[0] for c in at.calls]
        assert "MDL_EXP_CMPL" in codes

    def test_export_start_sends_format_ack(self):
        """report_export_start sends MDL_EXPT_ACK."""
        at = MockActionTracker()
        tracker = ExportTracker(action_tracker=at)
        tracker.report_export_start("onnx")

        codes = [c[0] for c in at.calls]
        assert "MDL_EXPT_ACK" in codes

    def test_export_success_sends_format_result_success(self):
        """report_export_success sends MDL_EXPT with SUCCESS status."""
        at = MockActionTracker()
        tracker = ExportTracker(action_tracker=at)
        tracker.report_export_start("onnx")
        tracker.report_export_success("onnx", "/tmp/model.onnx")

        result_calls = [c for c in at.calls if c[0] == "MDL_EXPT"]
        assert len(result_calls) == 1
        assert result_calls[0][1] == "SUCCESS"

    def test_export_failure_sends_format_result_error(self):
        """report_export_failure sends MDL_EXPT with ERROR status."""
        at = MockActionTracker()
        tracker = ExportTracker(action_tracker=at)
        tracker.report_export_start("onnx")
        tracker.report_export_failure("onnx", "export crashed")

        result_calls = [c for c in at.calls if c[0] == "MDL_EXPT"]
        assert len(result_calls) == 1
        assert result_calls[0][1] == "ERROR"
        assert "export crashed" in result_calls[0][2]


# ------------------------------------------------------------------ #
# 3. Timing in descriptions
# ------------------------------------------------------------------ #


class TestExportTrackerTiming:
    def test_pipeline_complete_includes_elapsed(self):
        """The pipeline complete description includes elapsed time."""
        at = MockActionTracker()
        tracker = ExportTracker(action_tracker=at)
        tracker.report_pipeline_start(["onnx"])
        time.sleep(0.05)  # small delay so elapsed > 0
        tracker.report_pipeline_complete({"onnx": {"status": "success"}})

        cmpl_calls = [c for c in at.calls if c[0] == "MDL_EXP_CMPL"]
        assert len(cmpl_calls) == 1
        assert "in " in cmpl_calls[0][2]  # "in X.Xs"

    def test_export_success_includes_elapsed(self):
        """The format success description includes elapsed time."""
        at = MockActionTracker()
        tracker = ExportTracker(action_tracker=at)
        tracker.report_export_start("onnx")
        time.sleep(0.05)
        tracker.report_export_success("onnx", "/tmp/model.onnx")

        result_calls = [c for c in at.calls if c[0] == "MDL_EXPT"]
        assert len(result_calls) == 1
        assert "in " in result_calls[0][2]


# ------------------------------------------------------------------ #
# 4. Exception handling — tracker exceptions do not propagate
# ------------------------------------------------------------------ #


class TestExportTrackerExceptionHandling:
    def test_tracker_exception_does_not_propagate_on_start(self):
        """If action_tracker.update_status raises, pipeline_start catches it."""
        at = MagicMock()
        at.update_status.side_effect = RuntimeError("network down")
        tracker = ExportTracker(action_tracker=at)
        tracker.report_pipeline_start(["onnx"])  # should not raise

    def test_tracker_exception_does_not_propagate_on_complete(self):
        """If action_tracker.update_status raises, pipeline_complete catches it."""
        at = MagicMock()
        at.update_status.side_effect = RuntimeError("network down")
        tracker = ExportTracker(action_tracker=at)
        tracker.report_pipeline_complete({"onnx": {"status": "success"}})

    def test_tracker_exception_does_not_propagate_on_export(self):
        """If action_tracker.update_status raises, export start/success/failure catch it."""
        at = MagicMock()
        at.update_status.side_effect = RuntimeError("network down")
        tracker = ExportTracker(action_tracker=at)
        tracker.report_export_start("onnx")
        tracker.report_export_success("onnx", "/tmp/model.onnx")
        tracker.report_export_failure("onnx", "some error")


# ------------------------------------------------------------------ #
# 5. Pipeline complete status logic
# ------------------------------------------------------------------ #


class TestExportTrackerPipelineCompleteStatus:
    def test_all_success(self):
        """When all formats succeed, status is SUCCESS."""
        at = MockActionTracker()
        tracker = ExportTracker(action_tracker=at)
        tracker.report_pipeline_start(["onnx", "torchscript"])
        tracker.report_pipeline_complete({
            "onnx": {"status": "success"},
            "torchscript": {"status": "success"},
        })

        cmpl = [c for c in at.calls if c[0] == "MDL_EXP_CMPL"]
        assert cmpl[0][1] == "SUCCESS"

    def test_partial_failure(self):
        """When some succeed and some fail, status is SUCCESS (partial)."""
        at = MockActionTracker()
        tracker = ExportTracker(action_tracker=at)
        tracker.report_pipeline_start(["onnx", "torchscript"])
        tracker.report_pipeline_complete({
            "onnx": {"status": "success"},
            "torchscript": {"status": "error"},
        })

        cmpl = [c for c in at.calls if c[0] == "MDL_EXP_CMPL"]
        assert cmpl[0][1] == "SUCCESS"
        assert "1 failed" in cmpl[0][2]

    def test_all_failure(self):
        """When all formats fail, status is ERROR."""
        at = MockActionTracker()
        tracker = ExportTracker(action_tracker=at)
        tracker.report_pipeline_start(["onnx", "torchscript"])
        tracker.report_pipeline_complete({
            "onnx": {"status": "error"},
            "torchscript": {"status": "error"},
        })

        cmpl = [c for c in at.calls if c[0] == "MDL_EXP_CMPL"]
        assert cmpl[0][1] == "ERROR"

    def test_validation_info_in_success_desc(self):
        """When validation info is provided, it appears in the description."""
        at = MockActionTracker()
        tracker = ExportTracker(action_tracker=at)
        tracker.report_export_start("onnx")
        tracker.report_export_success("onnx", "/tmp/model.onnx", {"status": "pass"})

        result = [c for c in at.calls if c[0] == "MDL_EXPT"]
        assert "pass" in result[0][2]
