from datetime import UTC, datetime, timedelta
import pytest
from unittest.mock import MagicMock, patch

from src.models.llm import ValidationRecord
from src.services.health_service import HealthService

# region test_health_service] [TYPE Module]
# @PURPOSE: Unit tests for HealthService aggregation logic.
# @RELATION  BINDS_TO ->[HealthService]


@pytest.mark.asyncio
async def test_get_health_summary_aggregation():
    """
    @TEST_SCENARIO: Verify that HealthService correctly aggregates the latest record per dashboard.
    """
    # Setup: Mock DB session
    db = MagicMock()

    now = datetime.now(UTC)

    # Dashboard 1: Old FAIL, New PASS
    rec1_old = ValidationRecord(
        id="rec-old",
        dashboard_id="dash_1",
        environment_id="env_1",
        status="FAIL",
        timestamp=now - timedelta(hours=1),
        summary="Old failure",
        issues=[],
    )
    rec1_new = ValidationRecord(
        id="rec-new",
        dashboard_id="dash_1",
        environment_id="env_1",
        status="PASS",
        timestamp=now,
        summary="New pass",
        issues=[],
    )

    # Dashboard 2: Single WARN
    rec2 = ValidationRecord(
        id="rec-warn",
        dashboard_id="dash_2",
        environment_id="env_1",
        status="WARN",
        timestamp=now,
        summary="Warning",
        issues=[],
    )

    # Mock the query chain
    # subquery = self.db.query(...).filter(...).group_by(...).subquery()
    # query = self.db.query(ValidationRecord).join(subquery, ...).all()

    mock_query = db.query.return_value
    mock_query.filter.return_value = mock_query
    mock_query.group_by.return_value = mock_query
    mock_query.subquery.return_value = MagicMock()

    db.query.return_value.join.return_value.all.return_value = [rec1_new, rec2]

    service = HealthService(db)
    summary = await service.get_health_summary(environment_id="env_1")

    assert summary.pass_count == 1
    assert summary.warn_count == 1
    assert summary.fail_count == 0
    assert len(summary.items) == 2

    # Verify dash_1 has the latest status (PASS)
    dash_1_item = next(item for item in summary.items if item.dashboard_id == "dash_1")
    assert dash_1_item.status == "PASS"
    assert dash_1_item.summary == "New pass"
    assert dash_1_item.record_id == rec1_new.id
    assert dash_1_item.dashboard_slug == "dash_1"


@pytest.mark.asyncio
async def test_get_health_summary_empty():
    """
    @TEST_SCENARIO: Verify behavior with no records.
    """
    db = MagicMock()
    db.query.return_value.join.return_value.all.return_value = []

    service = HealthService(db)
    summary = await service.get_health_summary(environment_id="env_none")

    assert summary.pass_count == 0
    assert len(summary.items) == 0


@pytest.mark.asyncio
async def test_get_health_summary_resolves_slug_and_title_from_superset():
    db = MagicMock()
    config_manager = MagicMock()
    config_manager.get_environments.return_value = [MagicMock(id="env_1")]

    record = ValidationRecord(
        id="rec-1",
        dashboard_id="42",
        environment_id="env_1",
        status="PASS",
        timestamp=datetime.now(UTC),
        summary="Healthy",
        issues=[],
    )
    db.query.return_value.join.return_value.all.return_value = [record]

    with patch("src.services.health_service.SupersetClient") as mock_client_cls:
        mock_client = MagicMock()
        mock_client.get_dashboards_summary.return_value = [
            {"id": 42, "slug": "ops-overview", "title": "Ops Overview"}
        ]
        mock_client_cls.return_value = mock_client

        service = HealthService(db, config_manager=config_manager)
        summary = await service.get_health_summary(environment_id="env_1")

    assert summary.items[0].dashboard_slug == "ops-overview"
    assert summary.items[0].dashboard_title == "Ops Overview"
    mock_client.get_dashboards_summary.assert_called_once_with()


@pytest.mark.anyio
async def test_get_health_summary_reuses_dashboard_metadata_cache_across_service_instances():
    HealthService._dashboard_summary_cache.clear()

    db = MagicMock()
    config_manager = MagicMock()
    config_manager.get_environments.return_value = [MagicMock(id="env_1")]
    record = ValidationRecord(
        id="rec-1",
        dashboard_id="42",
        environment_id="env_1",
        status="PASS",
        timestamp=datetime.now(UTC),
        summary="Healthy",
        issues=[],
    )
    db.query.return_value.join.return_value.all.return_value = [record]

    with patch("src.services.health_service.SupersetClient") as mock_client_cls:
        mock_client = MagicMock()
        mock_client.get_dashboards_summary.return_value = [
            {"id": 42, "slug": "ops-overview", "title": "Ops Overview"}
        ]
        mock_client_cls.return_value = mock_client

        first_service = HealthService(db, config_manager=config_manager)
        second_service = HealthService(db, config_manager=config_manager)

        first_summary = await first_service.get_health_summary(environment_id="env_1")
        second_summary = await second_service.get_health_summary(environment_id="env_1")

    assert first_summary.items[0].dashboard_slug == "ops-overview"
    assert second_summary.items[0].dashboard_slug == "ops-overview"
    mock_client.get_dashboards_summary.assert_called_once_with()
    HealthService._dashboard_summary_cache.clear()


# region test_delete_validation_report_deletes_dashboard_scope_and_linked_tasks [TYPE Function]
# @RELATION  BINDS_TO -> [HealthService]
# @PURPOSE: Verify that deleting a validation report also removes dashboard scope and linked tasks.
def test_delete_validation_report_deletes_dashboard_scope_and_linked_tasks():
    db = MagicMock()
    config_manager = MagicMock()
    task_manager = MagicMock()
    task_manager.tasks = {"task-1": object(), "task-2": object(), "task-3": object()}

    target_record = ValidationRecord(
        id="rec-1",
        task_id="task-1",
        dashboard_id="42",
        environment_id="env_1",
        status="PASS",
        timestamp=datetime.now(UTC),
        summary="Healthy",
        issues=[],
        screenshot_path=None,
    )
    older_peer = ValidationRecord(
        id="rec-2",
        task_id="task-2",
        dashboard_id="42",
        environment_id="env_1",
        status="FAIL",
        timestamp=datetime.now(UTC) - timedelta(hours=1),
        summary="Older",
        issues=[],
        screenshot_path=None,
    )
    other_environment = ValidationRecord(
        id="rec-3",
        task_id="task-3",
        dashboard_id="42",
        environment_id="env_2",
        status="WARN",
        timestamp=datetime.now(UTC),
        summary="Other environment",
        issues=[],
        screenshot_path=None,
    )

    # @RISK: db.query side_effect chain may not propagate through .filter().first() — verify mock chain setup is correct for this test.
    first_query = MagicMock()
    first_query.first.return_value = target_record

    peer_query = MagicMock()
    peer_query.filter.return_value = peer_query
    peer_query.all.return_value = [target_record, older_peer]

    db.query.side_effect = [first_query, peer_query]

    with patch("src.services.health_service.TaskCleanupService") as cleanup_cls:
        cleanup_instance = MagicMock()
        cleanup_cls.return_value = cleanup_instance

        service = HealthService(db, config_manager=config_manager)
        deleted = service.delete_validation_report("rec-1", task_manager=task_manager)

    assert deleted is True
    assert db.delete.call_count == 2
    db.delete.assert_any_call(target_record)
    db.delete.assert_any_call(older_peer)
    db.commit.assert_called_once()
    cleanup_instance.delete_task_with_logs.assert_any_call("task-1")
    cleanup_instance.delete_task_with_logs.assert_any_call("task-2")
    assert cleanup_instance.delete_task_with_logs.call_count == 2
    assert "task-1" not in task_manager.tasks
    assert "task-2" not in task_manager.tasks
    assert "task-3" in task_manager.tasks


# endregion test_delete_validation_report_deletes_dashboard_scope_and_linked_tasks


# region test_delete_validation_report_returns_false_for_unknown_record [TYPE Function]
# @RELATION  BINDS_TO -> [HealthService]
# @PURPOSE: Verify delete returns False when validation record does not exist.
def test_delete_validation_report_returns_false_for_unknown_record():
    db = MagicMock()
    db.query.return_value.filter.return_value.first.return_value = None

    service = HealthService(db, config_manager=MagicMock())

    assert service.delete_validation_report("missing") is False


# endregion test_delete_validation_report_returns_false_for_unknown_record


# region test_delete_validation_report_swallows_linked_task_cleanup_failure [TYPE Function]
# @RELATION  BINDS_TO -> [HealthService]
# @PURPOSE: Verify delete swallows exceptions when cleaning up linked tasks.
def test_delete_validation_report_swallows_linked_task_cleanup_failure():
    db = MagicMock()
    config_manager = MagicMock()
    task_manager = MagicMock()
    task_manager.tasks = {"task-1": object()}

    record = ValidationRecord(
        id="rec-1",
        task_id="task-1",
        dashboard_id="42",
        environment_id="env_1",
        status="PASS",
        timestamp=datetime.now(UTC),
        summary="Healthy",
        issues=[],
        screenshot_path=None,
    )

    # @RISK: db.query side_effect chain may not propagate through .filter().first() — verify mock chain setup is correct for this test.
    first_query = MagicMock()
    first_query.first.return_value = record

    peer_query = MagicMock()
    peer_query.filter.return_value = peer_query
    peer_query.all.return_value = [record]

    db.query.side_effect = [first_query, peer_query]

    with (
        patch("src.services.health_service.TaskCleanupService") as cleanup_cls,
        patch("src.services.health_service.logger") as mock_logger,
    ):
        cleanup_instance = MagicMock()
        cleanup_instance.delete_task_with_logs.side_effect = RuntimeError(
            "cleanup exploded"
        )
        cleanup_cls.return_value = cleanup_instance

        service = HealthService(db, config_manager=config_manager)
        deleted = service.delete_validation_report("rec-1", task_manager=task_manager)

    assert deleted is True
    db.delete.assert_called_once_with(record)
    db.commit.assert_called_once()
    cleanup_instance.delete_task_with_logs.assert_called_once_with("task-1")
    mock_logger.warning.assert_called_once()
    assert "task-1" not in task_manager.tasks


# endregion test_delete_validation_report_swallows_linked_task_cleanup_failure
# endregion test_health_service]
