# region TestResourceService [TYPE Module]
# @SEMANTICS: resource-service, tests, dashboards, datasets, activity
# @PURPOSE: Unit tests for ResourceService
# @LAYER Service
# @RELATION  BINDS_TO ->[ResourceService]
# @INVARIANT  Resource summaries preserve task linkage and status projection behavior.

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


# region test_get_dashboards_with_status [TYPE Function]
# @RELATION  BINDS_TO ->[EXT:frontend:TestResourceService]
# @PURPOSE: Validate dashboard enrichment includes git/task status projections.
# @TEST: get_dashboards_with_status returns dashboards with git and task status
# @PRE  SupersetClient returns dashboard list
# @POST  Each dashboard has git_status and last_task fields
@pytest.mark.asyncio
async def test_get_dashboards_with_status():
    with (
        patch("src.services.resource_service.SupersetClient") as mock_client,
        patch("src.services.resource_service.GitService"),
    ):
        from src.services.resource_service import ResourceService

        service = ResourceService()

        # Mock Superset response
        mock_client.return_value.get_dashboards_summary.return_value = [
            {"id": 1, "title": "Dashboard 1", "slug": "dash-1"},
            {"id": 2, "title": "Dashboard 2", "slug": "dash-2"},
        ]

        # Mock tasks
        task_prod_old = MagicMock()
        task_prod_old.id = "task-123"
        task_prod_old.plugin_id = "llm_dashboard_validation"
        task_prod_old.status = "SUCCESS"
        task_prod_old.params = {"dashboard_id": "1", "environment_id": "prod"}
        task_prod_old.started_at = datetime(2024, 1, 1, 10, 0, 0)

        task_prod_new = MagicMock()
        task_prod_new.id = "task-124"
        task_prod_new.plugin_id = "llm_dashboard_validation"
        task_prod_new.status = "TaskStatus.FAILED"
        task_prod_new.params = {"dashboard_id": "1", "environment_id": "prod"}
        task_prod_new.result = {"status": "FAIL"}
        task_prod_new.started_at = datetime(2024, 1, 1, 12, 0, 0)

        task_other_env = MagicMock()
        task_other_env.id = "task-200"
        task_other_env.plugin_id = "llm_dashboard_validation"
        task_other_env.status = "SUCCESS"
        task_other_env.params = {"dashboard_id": "1", "environment_id": "stage"}
        task_other_env.started_at = datetime(2024, 1, 1, 13, 0, 0)

        env = MagicMock()
        env.id = "prod"

        result = await service.get_dashboards_with_status(
            env,
            [task_prod_old, task_prod_new, task_other_env],
        )

        assert len(result) == 2
        assert result[0]["id"] == 1
        assert "git_status" in result[0]
        assert "last_task" in result[0]
        assert result[0]["last_task"]["task_id"] == "task-124"
        assert result[0]["last_task"]["status"] == "FAILED"
        assert result[0]["last_task"]["validation_status"] == "FAIL"


# endregion test_get_dashboards_with_status


# region test_get_datasets_with_status [TYPE Function]
# @RELATION  BINDS_TO ->[EXT:frontend:TestResourceService]
# @TEST: get_datasets_with_status returns datasets with task status
# @PRE  SupersetClient returns dataset list
# @POST  Each dataset has last_task field
# @PURPOSE: Verify ResourceService.get_datasets_with_status returns datasets grouped by validation status.
@pytest.mark.asyncio
async def test_get_datasets_with_status():
    with patch("src.services.resource_service.SupersetClient") as mock_client:
        from src.services.resource_service import ResourceService

        service = ResourceService()

        # Mock Superset response
        mock_client.return_value.get_datasets_summary.return_value = [
            {"id": 1, "table_name": "users", "schema": "public", "database": "app"},
            {"id": 2, "table_name": "orders", "schema": "public", "database": "app"},
        ]

        # Mock tasks
        mock_task = MagicMock()
        mock_task.id = "task-456"
        mock_task.status = "RUNNING"
        mock_task.params = {"resource_id": "dataset-1"}
        mock_task.created_at = datetime.now()

        env = MagicMock()
        env.id = "prod"

        result = await service.get_datasets_with_status(env, [mock_task])

        assert len(result) == 2
        assert result[0]["table_name"] == "users"
        assert "last_task" in result[0]
        assert result[0]["last_task"]["task_id"] == "task-456"
        assert result[0]["last_task"]["status"] == "RUNNING"


# endregion test_get_datasets_with_status


# region test_get_activity_summary [TYPE Function]
# @RELATION  BINDS_TO ->[EXT:frontend:TestResourceService]
# @TEST: get_activity_summary returns active count and recent tasks
# @PRE  tasks list provided
# @POST  Returns dict with active_count and recent_tasks
# @PURPOSE: Verify ResourceService.get_activity_summary returns recent task activity.
def test_get_activity_summary():
    from src.services.resource_service import ResourceService

    service = ResourceService()

    # Create mock tasks
    task1 = MagicMock()
    task1.id = "task-1"
    task1.status = "RUNNING"
    task1.params = {"resource_name": "Dashboard 1", "resource_type": "dashboard"}
    task1.created_at = datetime(2024, 1, 1, 10, 0, 0)

    task2 = MagicMock()
    task2.id = "task-2"
    task2.status = "SUCCESS"
    task2.params = {"resource_name": "Dataset 1", "resource_type": "dataset"}
    task2.created_at = datetime(2024, 1, 1, 9, 0, 0)

    task3 = MagicMock()
    task3.id = "task-3"
    task3.status = "WAITING_INPUT"
    task3.params = {"resource_name": "Dashboard 2", "resource_type": "dashboard"}
    task3.created_at = datetime(2024, 1, 1, 8, 0, 0)

    result = service.get_activity_summary([task1, task2, task3])

    assert result["active_count"] == 2  # RUNNING + WAITING_INPUT
    assert len(result["recent_tasks"]) == 3


# endregion test_get_activity_summary


# region test_get_git_status_for_dashboard_no_repo [TYPE Function]
# @RELATION  BINDS_TO ->[EXT:frontend:TestResourceService]
# @TEST: _get_git_status_for_dashboard returns None when no repo exists
# @PRE  GitService returns None for repo
# @POST  Returns None
# @PURPOSE: Verify get_git_status_for_dashboard returns None when no repo exists.
def test_get_git_status_for_dashboard_no_repo():
    with patch("src.services.resource_service.GitService") as mock_git:
        from src.services.resource_service import ResourceService

        service = ResourceService()
        mock_git.return_value.get_repo.return_value = None

        result = service._get_git_status_for_dashboard(123)

        assert result is not None
        assert result["sync_status"] == "NO_REPO"
        assert result["has_repo"] is False


# endregion test_get_git_status_for_dashboard_no_repo


# region test_get_last_task_for_resource [TYPE Function]
# @RELATION  BINDS_TO ->[EXT:frontend:TestResourceService]
# @TEST: _get_last_task_for_resource returns most recent task for resource
# @PRE  tasks list with matching resource_id
# @POST  Returns task summary with task_id and status
# @PURPOSE: Verify get_last_task_for_resource returns the most recent task for a given resource.
def test_get_last_task_for_resource():
    from src.services.resource_service import ResourceService

    service = ResourceService()

    # Create mock tasks
    task1 = MagicMock()
    task1.id = "task-old"
    task1.status = "SUCCESS"
    task1.params = {"resource_id": "dashboard-1"}
    task1.created_at = datetime(2024, 1, 1, 10, 0, 0)

    task2 = MagicMock()
    task2.id = "task-new"
    task2.status = "RUNNING"
    task2.params = {"resource_id": "dashboard-1"}
    task2.created_at = datetime(2024, 1, 1, 12, 0, 0)

    result = service._get_last_task_for_resource("dashboard-1", [task1, task2])

    assert result is not None
    assert result["task_id"] == "task-new"  # Most recent
    assert result["status"] == "RUNNING"


# endregion test_get_last_task_for_resource


# region test_extract_resource_name_from_task [TYPE Function]
# @RELATION  BINDS_TO ->[EXT:frontend:TestResourceService]
# @TEST: _extract_resource_name_from_task extracts name from params
# @PRE  task has resource_name in params
# @POST  Returns resource name or fallback
# @PURPOSE: Verify extract_resource_name_from_task correctly parses resource names from task identifiers.
def test_extract_resource_name_from_task():
    from src.services.resource_service import ResourceService

    service = ResourceService()

    # Task with resource_name
    task = MagicMock()
    task.id = "task-123"
    task.params = {"resource_name": "My Dashboard"}

    result = service._extract_resource_name_from_task(task)
    assert result == "My Dashboard"

    # Task without resource_name
    task2 = MagicMock()
    task2.id = "task-456"
    task2.params = {}

    result2 = service._extract_resource_name_from_task(task2)
    assert "task-456" in result2


# endregion test_extract_resource_name_from_task


# region test_get_last_task_for_resource_empty_tasks [TYPE Function]
# @RELATION  BINDS_TO ->[EXT:frontend:TestResourceService]
# @TEST: _get_last_task_for_resource returns None for empty tasks list
# @PRE  tasks is empty list
# @POST  Returns None
# @PURPOSE: Verify get_last_task_for_resource returns None when tasks list is empty.
def test_get_last_task_for_resource_empty_tasks():
    from src.services.resource_service import ResourceService

    service = ResourceService()

    result = service._get_last_task_for_resource("dashboard-1", [])
    assert result is None


# endregion test_get_last_task_for_resource_empty_tasks


# region test_get_last_task_for_resource_no_match [TYPE Function]
# @RELATION  BINDS_TO ->[EXT:frontend:TestResourceService]
# @TEST: _get_last_task_for_resource returns None when no tasks match resource_id
# @PRE  tasks list has no matching resource_id
# @POST  Returns None
# @PURPOSE: Verify get_last_task_for_resource returns None when no task matches the resource.
def test_get_last_task_for_resource_no_match():
    from src.services.resource_service import ResourceService

    service = ResourceService()

    task = MagicMock()
    task.id = "task-999"
    task.status = "SUCCESS"
    task.params = {"resource_id": "dashboard-99"}
    task.created_at = datetime(2024, 1, 1, 10, 0, 0)

    result = service._get_last_task_for_resource("dashboard-1", [task])
    assert result is None


# endregion test_get_last_task_for_resource_no_match


# region test_get_dashboards_with_status_handles_mixed_naive_and_aware_task_datetimes [TYPE Function]
# @RELATION  BINDS_TO ->[EXT:frontend:TestResourceService]
# @TEST: get_dashboards_with_status handles mixed naive/aware datetimes without comparison errors.
# @PRE  Task list includes both timezone-aware and timezone-naive timestamps.
# @POST  Latest task is selected deterministically and no exception is raised.
# @PURPOSE: Verify get_dashboards_with_status handles mixed naive and aware datetimes without crashing.
@pytest.mark.asyncio
async def test_get_dashboards_with_status_handles_mixed_naive_and_aware_task_datetimes():
    with (
        patch("src.services.resource_service.SupersetClient") as mock_client,
        patch("src.services.resource_service.GitService"),
    ):
        from src.services.resource_service import ResourceService

        service = ResourceService()
        mock_client.return_value.get_dashboards_summary.return_value = [
            {"id": 1, "title": "Dashboard 1", "slug": "dash-1"}
        ]

        task_naive = MagicMock()
        task_naive.id = "task-naive"
        task_naive.plugin_id = "llm_dashboard_validation"
        task_naive.status = "SUCCESS"
        task_naive.params = {"dashboard_id": "1", "environment_id": "prod"}
        task_naive.started_at = datetime(2024, 1, 1, 10, 0, 0)

        task_aware = MagicMock()
        task_aware.id = "task-aware"
        task_aware.plugin_id = "llm_dashboard_validation"
        task_aware.status = "SUCCESS"
        task_aware.params = {"dashboard_id": "1", "environment_id": "prod"}
        task_aware.started_at = datetime(2024, 1, 1, 12, 0, 0, tzinfo=UTC)

        env = MagicMock()
        env.id = "prod"

        result = await service.get_dashboards_with_status(env, [task_naive, task_aware])

        assert result[0]["last_task"]["task_id"] == "task-aware"


# endregion test_get_dashboards_with_status_handles_mixed_naive_and_aware_task_datetimes


# region test_get_dashboards_with_status_prefers_latest_decisive_validation_status_over_newer_unknown [TYPE Function]
# @RELATION  BINDS_TO ->[EXT:frontend:TestResourceService]
# @TEST: get_dashboards_with_status keeps latest task identity while falling back to older decisive validation status.
# @PRE  Same dashboard has older WARN and newer UNKNOWN validation tasks.
# @POST  Returned last_task points to newest task but preserves WARN as last meaningful validation state.
# @PURPOSE: Verify status ranking prefers decisive validation over newer unknown status.
@pytest.mark.anyio
async def test_get_dashboards_with_status_prefers_latest_decisive_validation_status_over_newer_unknown():
    with (
        patch("src.services.resource_service.SupersetClient") as mock_client,
        patch("src.services.resource_service.GitService"),
    ):
        from src.services.resource_service import ResourceService

        service = ResourceService()
        mock_client.return_value.get_dashboards_summary.return_value = [
            {"id": 4, "title": "Dashboard 4", "slug": "deck"}
        ]

        task_warn = MagicMock()
        task_warn.id = "task-warn"
        task_warn.plugin_id = "llm_dashboard_validation"
        task_warn.status = "SUCCESS"
        task_warn.params = {"dashboard_id": "4", "environment_id": "prod"}
        task_warn.result = {"status": "WARN"}
        task_warn.started_at = datetime(2024, 1, 1, 11, 0, 0)

        task_unknown = MagicMock()
        task_unknown.id = "task-unknown"
        task_unknown.plugin_id = "llm_dashboard_validation"
        task_unknown.status = "RUNNING"
        task_unknown.params = {"dashboard_id": "4", "environment_id": "prod"}
        task_unknown.result = {"status": "UNKNOWN"}
        task_unknown.started_at = datetime(2024, 1, 1, 12, 0, 0)

        env = MagicMock()
        env.id = "prod"

        result = await service.get_dashboards_with_status(
            env, [task_warn, task_unknown]
        )

        assert result[0]["last_task"]["task_id"] == "task-unknown"
        assert result[0]["last_task"]["status"] == "RUNNING"
        assert result[0]["last_task"]["validation_status"] == "WARN"


# endregion test_get_dashboards_with_status_prefers_latest_decisive_validation_status_over_newer_unknown


# region test_get_dashboards_with_status_falls_back_to_latest_unknown_without_decisive_history [TYPE Function]
# @RELATION  BINDS_TO ->[EXT:frontend:TestResourceService]
# @TEST: get_dashboards_with_status still returns newest UNKNOWN when no decisive validation exists.
# @PRE  Same dashboard has only UNKNOWN validation tasks.
# @POST  Returned last_task keeps newest UNKNOWN task.
# @PURPOSE: Verify fallback to latest unknown status when no decisive history exists.
@pytest.mark.anyio
async def test_get_dashboards_with_status_falls_back_to_latest_unknown_without_decisive_history():
    with (
        patch("src.services.resource_service.SupersetClient") as mock_client,
        patch("src.services.resource_service.GitService"),
    ):
        from src.services.resource_service import ResourceService

        service = ResourceService()
        mock_client.return_value.get_dashboards_summary.return_value = [
            {"id": 5, "title": "Dashboard 5", "slug": "ops"}
        ]

        task_unknown_old = MagicMock()
        task_unknown_old.id = "task-unknown-old"
        task_unknown_old.plugin_id = "llm_dashboard_validation"
        task_unknown_old.status = "SUCCESS"
        task_unknown_old.params = {"dashboard_id": "5", "environment_id": "prod"}
        task_unknown_old.result = {"status": "UNKNOWN"}
        task_unknown_old.started_at = datetime(2024, 1, 1, 11, 0, 0)

        task_unknown_new = MagicMock()
        task_unknown_new.id = "task-unknown-new"
        task_unknown_new.plugin_id = "llm_dashboard_validation"
        task_unknown_new.status = "SUCCESS"
        task_unknown_new.params = {"dashboard_id": "5", "environment_id": "prod"}
        task_unknown_new.result = {"status": "UNKNOWN"}
        task_unknown_new.started_at = datetime(2024, 1, 1, 12, 0, 0)

        env = MagicMock()
        env.id = "prod"

        result = await service.get_dashboards_with_status(
            env, [task_unknown_old, task_unknown_new]
        )

        assert result[0]["last_task"]["task_id"] == "task-unknown-new"
        assert result[0]["last_task"]["validation_status"] == "UNKNOWN"


# endregion test_get_dashboards_with_status_falls_back_to_latest_unknown_without_decisive_history


# region test_get_last_task_for_resource_handles_mixed_naive_and_aware_created_at [TYPE Function]
# @RELATION  BINDS_TO ->[EXT:frontend:TestResourceService]
# @TEST: _get_last_task_for_resource handles mixed naive/aware created_at values.
# @PRE  Matching tasks include naive and aware created_at timestamps.
# @POST  Latest task is returned without raising datetime comparison errors.
# @PURPOSE: Verify get_last_task_for_resource correctly sorts mixed naive and aware created_at timestamps.
def test_get_last_task_for_resource_handles_mixed_naive_and_aware_created_at():
    from src.services.resource_service import ResourceService

    service = ResourceService()

    task_naive = MagicMock()
    task_naive.id = "task-old"
    task_naive.status = "SUCCESS"
    task_naive.params = {"resource_id": "dashboard-1"}
    task_naive.created_at = datetime(2024, 1, 1, 10, 0, 0)

    task_aware = MagicMock()
    task_aware.id = "task-new"
    task_aware.status = "RUNNING"
    task_aware.params = {"resource_id": "dashboard-1"}
    task_aware.created_at = datetime(2024, 1, 1, 12, 0, 0, tzinfo=UTC)

    result = service._get_last_task_for_resource(
        "dashboard-1", [task_naive, task_aware]
    )

    assert result is not None
    assert result["task_id"] == "task-new"


# endregion test_get_last_task_for_resource_handles_mixed_naive_and_aware_created_at


# endregion TestResourceService
