# #region TestUpdateRunStatus [C:3] [TYPE Module] [SEMANTICS test,validation,run,status,counters]
# @BRIEF Verify _update_run_status computes counters correctly, sets status to 'completed',
#   and handles edge cases (missing run, partial records, exception handling).
# @RELATION BINDS_TO -> [LLMAnalysisPlugin]
# @TEST_EDGE: missing_run_id -> Returns immediately without DB query
# @TEST_EDGE: no_records_found -> Logs warning and returns
# @TEST_EDGE: partial_records -> Does NOT mark complete when records < dashboard_count
# @TEST_EDGE: exception_in_query -> Logs error, does not crash
# @TEST_INVARIANT: run_status_always_completed -> VERIFIED_BY: test_sets_status_completed_with_all_records
# @TEST_INVARIANT: counters_computed_from_records -> VERIFIED_BY: test_computed_counters_mixed_statuses, test_computed_counters_all_pass
# @TEST_INVARIANT: finished_at_set_on_completion -> VERIFIED_BY: test_sets_finished_at_utc

import sys
from datetime import UTC, datetime, timezone
from pathlib import Path
from unittest.mock import MagicMock, patch

import pytest

# Ensure src is importable (same pattern as test_llm_dashboard_validation_v2.py)
sys.path.insert(0, str(Path(__file__).parent.parent.parent / "src"))

from src.plugins.llm_analysis.plugin import _update_run_status


# Minimal mock models matching the ORM shape
class MockValidationRecord:
    def __init__(self, status, run_id="run-1"):
        self.status = status
        self.run_id = run_id


class MockValidationRun:
    def __init__(self, id="run-1", dashboard_count=3, status="running"):
        self.id = id
        self.dashboard_count = dashboard_count
        self.status = status
        self.finished_at = None
        self.pass_count = 0
        self.warn_count = 0
        self.fail_count = 0
        self.unknown_count = 0


def _make_mock_db(records=None, run=None):
    """Create a mock DB session that returns the given records and run."""
    mock_db = MagicMock()
    mock_query = MagicMock()
    mock_db.query.return_value = mock_query
    mock_query.filter.return_value = mock_query
    mock_query.all.return_value = records or []
    mock_query.first.return_value = run
    return mock_db


# #region TestUpdateRunStatusNoop [C:2] [TYPE Class]
# @BRIEF Verify _update_run_status early-exit conditions.
class TestUpdateRunStatusNoop:
    """Verify _update_run_status no-op paths."""

    # #region test_none_run_id_returns_early [C:2] [TYPE Function]
    # @BRIEF When run_id is None, function returns without DB access.
    def test_none_run_id_returns_early(self):
        mock_db = MagicMock()
        _update_run_status(mock_db, None)
        mock_db.query.assert_not_called()
    # #endregion test_none_run_id_returns_early

    # #region test_empty_run_id_returns_early [C:2] [TYPE Function]
    # @BRIEF When run_id is empty string, function returns without DB access.
    def test_empty_run_id_returns_early(self):
        mock_db = MagicMock()
        _update_run_status(mock_db, "")
        mock_db.query.assert_not_called()
    # #endregion test_empty_run_id_returns_early

    # #region test_no_records_logs_warning [C:2] [TYPE Function]
    # @BRIEF When no records exist for the run, logs warning and returns.
    def test_no_records_logs_warning(self):
        mock_db = _make_mock_db(records=[], run=MockValidationRun())

        _update_run_status(mock_db, "run-1")
        mock_db.commit.assert_not_called()
    # #endregion test_no_records_logs_warning

    # #region test_no_run_found_logs_warning [C:2] [TYPE Function]
    # @BRIEF When run record not found, logs warning and returns.
    def test_no_run_found_logs_warning(self):
        mock_db = _make_mock_db(records=[MockValidationRecord("PASS")], run=None)

        _update_run_status(mock_db, "run-1")
        mock_db.commit.assert_not_called()
    # #endregion test_no_run_found_logs_warning
# #endregion TestUpdateRunStatusNoop


# #region TestUpdateRunStatusPartial [C:2] [TYPE Class]
# @BRIEF Verify _update_run_status does NOT mark complete when records < dashboard_count.
class TestUpdateRunStatusPartial:
    """Verify partial completion guard."""

    # #region test_partial_records_not_marked_complete [C:2] [TYPE Function]
    # @BRIEF When len(records) < dashboard_count, run stays 'running'.
    def test_partial_records_not_marked_complete(self):
        run = MockValidationRun(dashboard_count=3, status="running")
        records = [MockValidationRecord("PASS"), MockValidationRecord("WARN")]  # 2 < 3
        mock_db = _make_mock_db(records=records, run=run)

        _update_run_status(mock_db, "run-1")

        assert run.status == "running"  # NOT changed
        mock_db.commit.assert_not_called()
    # #endregion test_partial_records_not_marked_complete

    # #region test_zero_dashboard_count_skips_guard [C:2] [TYPE Function]
    # @BRIEF When dashboard_count is 0 or None, the partial guard is skipped and run completes.
    def test_zero_dashboard_count_skips_guard(self):
        run = MockValidationRun(dashboard_count=0, status="running")
        records = [MockValidationRecord("PASS")]
        mock_db = _make_mock_db(records=records, run=run)

        _update_run_status(mock_db, "run-1")

        assert run.status == "completed"
        mock_db.commit.assert_called_once()
    # #endregion test_zero_dashboard_count_skips_guard
# #endregion TestUpdateRunStatusPartial


# #region TestUpdateRunStatusCompletion [C:2] [TYPE Class]
# @BRIEF Verify _update_run_status correctly computes counters and sets status.
class TestUpdateRunStatusCompletion:
    """Verify counter computation and status assignment."""

    # #region test_sets_status_completed_with_all_records [C:2] [TYPE Function]
    # @BRIEF When all expected records exist, run.status is set to 'completed'.
    def test_sets_status_completed_with_all_records(self):
        run = MockValidationRun(dashboard_count=2, status="running")
        records = [MockValidationRecord("PASS"), MockValidationRecord("FAIL")]
        mock_db = _make_mock_db(records=records, run=run)

        _update_run_status(mock_db, "run-1")

        assert run.status == "completed"
    # #endregion test_sets_status_completed_with_all_records

    # #region test_computed_counters_all_pass [C:2] [TYPE Function]
    # @BRIEF All PASS records → pass_count=2, others=0.
    def test_computed_counters_all_pass(self):
        run = MockValidationRun(dashboard_count=2)
        records = [MockValidationRecord("PASS"), MockValidationRecord("PASS")]
        mock_db = _make_mock_db(records=records, run=run)

        _update_run_status(mock_db, "run-1")

        assert run.pass_count == 2
        assert run.warn_count == 0
        assert run.fail_count == 0
        assert run.unknown_count == 0
    # #endregion test_computed_counters_all_pass

    # #region test_computed_counters_mixed_statuses [C:2] [TYPE Function]
    # @BRIEF Mixed statuses → correct per-status counters.
    def test_computed_counters_mixed_statuses(self):
        run = MockValidationRun(dashboard_count=4)
        records = [
            MockValidationRecord("PASS"),
            MockValidationRecord("PASS"),
            MockValidationRecord("WARN"),
            MockValidationRecord("FAIL"),
        ]
        mock_db = _make_mock_db(records=records, run=run)

        _update_run_status(mock_db, "run-1")

        assert run.pass_count == 2
        assert run.warn_count == 1
        assert run.fail_count == 1
        assert run.unknown_count == 0
    # #endregion test_computed_counters_mixed_statuses

    # #region test_computed_counters_unknown_status [C:2] [TYPE Function]
    # @BRIEF UNKNOWN status records are counted correctly.
    def test_computed_counters_unknown_status(self):
        run = MockValidationRun(dashboard_count=2)
        records = [MockValidationRecord("UNKNOWN"), MockValidationRecord("PASS")]
        mock_db = _make_mock_db(records=records, run=run)

        _update_run_status(mock_db, "run-1")

        assert run.unknown_count == 1
        assert run.pass_count == 1
    # #endregion test_computed_counters_unknown_status

    # #region test_sets_finished_at [C:2] [TYPE Function]
    # @BRIEF finished_at is set to a datetime on completion.
    def test_sets_finished_at(self):
        run = MockValidationRun(dashboard_count=1, status="running")
        records = [MockValidationRecord("PASS")]
        mock_db = _make_mock_db(records=records, run=run)

        _update_run_status(mock_db, "run-1")

        assert run.finished_at is not None
        assert isinstance(run.finished_at, datetime)
    # #endregion test_sets_finished_at

    # #region test_db_exception_logged_not_raised [C:2] [TYPE Function]
    # @BRIEF DB exception during finalization is logged but not re-raised.
    def test_db_exception_logged_not_raised(self):
        mock_db = MagicMock()
        mock_query = MagicMock()
        mock_db.query.return_value = mock_query
        mock_query.filter.return_value = mock_query
        mock_query.all.side_effect = RuntimeError("DB connection lost")

        # Should NOT raise
        _update_run_status(mock_db, "run-1")
    # #endregion test_db_exception_logged_not_raised
# #endregion TestUpdateRunStatusCompletion


# #region TestExecuteErrorHandling [C:2] [TYPE Class]
# @BRIEF Verify the always-finalize pattern used in execute().
class TestExecuteErrorHandling:
    """Verify _update_run_status is always callable after the loop."""

    # #region test_update_run_status_called_on_success [C:2] [TYPE Function]
    # @BRIEF _update_run_status is called after normal execution with all records.
    def test_update_run_status_called_on_success(self):
        run = MockValidationRun(dashboard_count=1, status="running")
        records = [MockValidationRecord("PASS")]
        mock_db = _make_mock_db(records=records, run=run)

        _update_run_status(mock_db, "run-1")
        mock_db.commit.assert_called_once()
        assert run.status == "completed"
    # #endregion test_update_run_status_called_on_success

    # #region test_update_run_status_handles_zero_records [C:2] [TYPE Function]
    # @BRIEF When all dashboards failed (0 records), _update_run_status still runs
    #   and does not mark the run as complete (partial guard).
    def test_update_run_status_handles_zero_records(self):
        run = MockValidationRun(dashboard_count=2, status="running")
        mock_db = _make_mock_db(records=[], run=run)

        _update_run_status(mock_db, "run-1")

        # With 0 records and dashboard_count=2, the partial guard prevents completion
        assert run.status == "running"
        mock_db.commit.assert_not_called()
    # #endregion test_update_run_status_handles_zero_records
# #endregion TestExecuteErrorHandling


# #endregion TestUpdateRunStatus
