# #region Test.ReportsService [C:3] [TYPE Module] [SEMANTICS test,report,service,pagination,filter]
# @BRIEF Tests for services/reports/report_service.py — ReportsService.
# @RELATION BINDS_TO -> [report_service]

from pathlib import Path
import sys

sys.path.insert(0, str(Path(__file__).parent.parent.parent / "src"))

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


# #region helper factories [C:1] [TYPE Function]
def _make_task(**overrides):
    from src.core.task_manager.models import Task, TaskStatus
    defaults = {
        "id": "task-1",
        "plugin_id": "superset-migration",
        "status": TaskStatus.SUCCESS,
        "params": {},
        "result": {"summary": "Migration complete"},
        "started_at": datetime(2024, 1, 1, 12, 0, 0, tzinfo=UTC),
        "finished_at": datetime(2024, 1, 1, 12, 30, 0, tzinfo=UTC),
        "logs": [],
    }
    defaults.update(overrides)
    return Task(**defaults)


def _make_service(task_manager=None, clean_release_repo=None):
    from src.services.reports.report_service import ReportsService
    if task_manager is None:
        task_manager = MagicMock()
        task_manager.get_all_tasks.return_value = []
    return ReportsService(task_manager, clean_release_repo)
# #endregion helper factories


# #region test_reports_service_init [C:2] [TYPE Function]
# @BRIEF Test ReportsService initialization.
class TestReportsServiceInit:
    def test_stores_task_manager(self):
        tm = MagicMock()
        service = _make_service(task_manager=tm)
        assert service.task_manager is tm

    def test_stores_clean_release_repo(self):
        crr = MagicMock()
        service = _make_service(clean_release_repo=crr)
        assert service.clean_release_repository is crr

    def test_clean_release_repo_optional(self):
        service = _make_service()
        assert service.clean_release_repository is None
# #endregion test_reports_service_init


# #region test_load_normalized_reports [C:2] [TYPE Function]
# @BRIEF Test _load_normalized_reports.
class TestLoadNormalizedReports:
    def test_returns_empty_list_for_no_tasks(self):
        tm = MagicMock()
        tm.get_all_tasks.return_value = []
        service = _make_service(task_manager=tm)
        reports = service._load_normalized_reports()
        assert reports == []

    def test_normalizes_all_tasks(self):
        tm = MagicMock()
        task = _make_task()
        tm.get_all_tasks.return_value = [task]
        service = _make_service(task_manager=tm)
        reports = service._load_normalized_reports()
        assert len(reports) == 1
        assert reports[0].report_id == task.id
# #endregion test_load_normalized_reports


# #region test_to_utc_datetime [C:2] [TYPE Function]
# @BRIEF Test _to_utc_datetime.
class TestToUtcDatetime:
    def test_none_returns_none(self):
        service = _make_service()
        assert service._to_utc_datetime(None) is None

    def test_naive_interpreted_as_utc(self):
        service = _make_service()
        dt = datetime(2024, 1, 1, 12, 0, 0)
        result = service._to_utc_datetime(dt)
        assert result.tzinfo is not None
        assert result.tzinfo == UTC

    def test_aware_converted_to_utc(self):
        service = _make_service()
        from zoneinfo import ZoneInfo
        dt = datetime(2024, 1, 1, 15, 0, 0, tzinfo=ZoneInfo("Europe/Moscow"))
        result = service._to_utc_datetime(dt)
        assert result.tzinfo == UTC
        assert result.hour == 12  # Moscow +3 -> UTC
# #endregion test_to_utc_datetime


# #region test_datetime_sort_key [C:2] [TYPE Function]
# @BRIEF Test _datetime_sort_key.
class TestDatetimeSortKey:
    def test_returns_timestamp(self):
        from src.models.report import TaskReport, ReportStatus, TaskType
        service = _make_service()
        report = MagicMock(spec=TaskReport)
        report.updated_at = datetime(2024, 1, 1, 12, 0, 0, tzinfo=UTC)
        key = service._datetime_sort_key(report)
        assert key == 1704110400.0

    def test_returns_zero_for_none(self):
        from src.models.report import TaskReport
        service = _make_service()
        report = MagicMock(spec=TaskReport)
        report.updated_at = None
        key = service._datetime_sort_key(report)
        assert key == 0.0
# #endregion test_datetime_sort_key


# #region test_matches_query [C:2] [TYPE Function]
# @BRIEF Test _matches_query.
class TestMatchesQuery:
    def test_no_filters_matches_all(self):
        from src.models.report import ReportQuery
        service = _make_service()
        report = MagicMock()
        query = ReportQuery()
        assert service._matches_query(report, query) is True

    def test_filters_by_task_type(self):
        from src.models.report import ReportQuery, TaskType
        service = _make_service()
        report = MagicMock()
        report.task_type = TaskType.MIGRATION
        query = ReportQuery(task_types=[TaskType.MIGRATION])
        assert service._matches_query(report, query) is True
        query = ReportQuery(task_types=[TaskType.BACKUP])
        assert service._matches_query(report, query) is False

    def test_filters_by_status(self):
        from src.models.report import ReportQuery, ReportStatus
        service = _make_service()
        report = MagicMock()
        report.status = ReportStatus.SUCCESS
        query = ReportQuery(statuses=[ReportStatus.SUCCESS])
        assert service._matches_query(report, query) is True
        query = ReportQuery(statuses=[ReportStatus.FAILED])
        assert service._matches_query(report, query) is False

    def test_filters_by_time_range(self):
        from src.models.report import ReportQuery
        service = _make_service()
        report = MagicMock()
        report.updated_at = datetime(2024, 6, 15, 12, 0, 0, tzinfo=UTC)
        # Time from before -> match
        query = ReportQuery(time_from=datetime(2024, 1, 1, tzinfo=UTC))
        assert service._matches_query(report, query) is True
        # Time from after -> no match
        query = ReportQuery(time_from=datetime(2024, 12, 1, tzinfo=UTC))
        assert service._matches_query(report, query) is False
        # Time to before -> no match
        query = ReportQuery(time_to=datetime(2024, 1, 1, tzinfo=UTC))
        assert service._matches_query(report, query) is False

    def test_filters_by_search(self):
        from src.models.report import ReportQuery, ReportStatus, TaskType
        service = _make_service()
        report = MagicMock()
        report.summary = "Migration completed"
        report.task_type = TaskType.MIGRATION
        report.status = ReportStatus.SUCCESS
        query = ReportQuery(search="migration")
        assert service._matches_query(report, query) is True
        query = ReportQuery(search="backup")
        assert service._matches_query(report, query) is False
# #endregion test_matches_query


# #region test_sort_reports [C:2] [TYPE Function]
# @BRIEF Test _sort_reports.
class TestSortReports:
    def test_sort_by_updated_at_desc_default(self):
        from src.models.report import ReportQuery
        service = _make_service()
        reports = [
            MagicMock(updated_at=datetime(2024, 1, 1, tzinfo=UTC)),
            MagicMock(updated_at=datetime(2024, 6, 15, tzinfo=UTC)),
            MagicMock(updated_at=datetime(2024, 3, 1, tzinfo=UTC)),
        ]
        query = ReportQuery()
        sorted_reports = service._sort_reports(reports, query)
        assert sorted_reports[0].updated_at > sorted_reports[-1].updated_at

    def test_sort_by_status_asc(self):
        from src.models.report import ReportQuery, ReportStatus
        service = _make_service()
        reports = [
            MagicMock(status=ReportStatus.FAILED, updated_at=datetime(2024, 1, 1, tzinfo=UTC)),
            MagicMock(status=ReportStatus.SUCCESS, updated_at=datetime(2024, 6, 15, tzinfo=UTC)),
        ]
        query = ReportQuery(sort_by="status", sort_order="asc")
        sorted_reports = service._sort_reports(reports, query)
        assert sorted_reports[0].status.value < sorted_reports[1].status.value

    def test_sort_by_task_type_desc(self):
        from src.models.report import ReportQuery, TaskType
        service = _make_service()
        reports = [
            MagicMock(task_type=TaskType.BACKUP, updated_at=datetime(2024, 1, 1, tzinfo=UTC)),
            MagicMock(task_type=TaskType.MIGRATION, updated_at=datetime(2024, 6, 15, tzinfo=UTC)),
        ]
        query = ReportQuery(sort_by="task_type", sort_order="desc")
        sorted_reports = service._sort_reports(reports, query)
        assert sorted_reports[0].task_type.value > sorted_reports[1].task_type.value
# #endregion test_sort_reports


# #region test_list_reports [C:2] [TYPE Function]
# @BRIEF Test list_reports with pagination.
class TestListReports:
    def test_returns_empty_collection(self):
        from src.models.report import ReportQuery
        tm = MagicMock()
        tm.get_all_tasks.return_value = []
        service = _make_service(task_manager=tm)
        result = service.list_reports(ReportQuery())
        assert result.total == 0
        assert result.items == []
        assert result.has_next is False

    def test_paginates_results(self):
        from src.models.report import ReportQuery
        tm = MagicMock()
        tasks = [_make_task(id=f"task-{i}", plugin_id="test") for i in range(5)]
        tm.get_all_tasks.return_value = tasks
        service = _make_service(task_manager=tm)
        query = ReportQuery(page=1, page_size=2)
        result = service.list_reports(query)
        assert result.total == 5
        assert len(result.items) == 2
        assert result.has_next is True

    def test_last_page_no_next(self):
        from src.models.report import ReportQuery
        tm = MagicMock()
        tasks = [_make_task(id=f"task-{i}", plugin_id="test") for i in range(3)]
        tm.get_all_tasks.return_value = tasks
        service = _make_service(task_manager=tm)
        query = ReportQuery(page=2, page_size=2)
        result = service.list_reports(query)
        assert result.total == 3
        assert len(result.items) == 1
        assert result.has_next is False

    def test_filters_applied(self):
        from src.models.report import ReportQuery, TaskType
        from src.core.task_manager.models import TaskStatus
        tm = MagicMock()
        tasks = [
            _make_task(id="task-backup", plugin_id="superset-backup"),
            _make_task(id="task-migration", plugin_id="superset-migration"),
        ]
        tm.get_all_tasks.return_value = tasks
        service = _make_service(task_manager=tm)
        query = ReportQuery(task_types=[TaskType.BACKUP])
        result = service.list_reports(query)
        assert result.total == 1
        assert result.items[0].task_type == TaskType.BACKUP

    def test_search_filter(self):
        from src.models.report import ReportQuery
        from src.core.task_manager.models import TaskStatus
        tm = MagicMock()
        tasks = [
            _make_task(id="t1", plugin_id="superset-migration", result={"summary": "Backup completed"}),
            _make_task(id="t2", plugin_id="superset-backup", result={"summary": "Migration done"}),
        ]
        tm.get_all_tasks.return_value = tasks
        service = _make_service(task_manager=tm)
        query = ReportQuery(search="backup")
        result = service.list_reports(query)
        # Both "Backup" and "backup" summaries match
        assert result.total >= 1
# #endregion test_list_reports


# #region test_get_report_detail [C:2] [TYPE Function]
# @BRIEF Test get_report_detail.
class TestGetReportDetail:
    def test_returns_none_for_missing(self):
        tm = MagicMock()
        tm.get_all_tasks.return_value = []
        service = _make_service(task_manager=tm)
        result = service.get_report_detail("nonexistent")
        assert result is None

    def test_returns_detail_for_existing(self):
        tm = MagicMock()
        task = _make_task(id="task-1")
        tm.get_all_tasks.return_value = [task]
        service = _make_service(task_manager=tm)
        result = service.get_report_detail("task-1")
        assert result is not None
        assert result.report.report_id == "task-1"
        assert len(result.timeline) == 2  # started + updated

    def test_recovery_actions_for_failed(self):
        from src.core.task_manager.models import TaskStatus
        tm = MagicMock()
        task = _make_task(id="task-1", status=TaskStatus.FAILED, result={"error": {"message": "Failed"}})
        tm.get_all_tasks.return_value = [task]
        service = _make_service(task_manager=tm)
        result = service.get_report_detail("task-1")
        assert result is not None
        assert len(result.next_actions) > 0

    def test_default_next_actions_for_failed(self):
        from src.core.task_manager.models import TaskStatus
        tm = MagicMock()
        task = _make_task(id="task-1", status=TaskStatus.FAILED, result=None)
        tm.get_all_tasks.return_value = [task]
        service = _make_service(task_manager=tm)
        result = service.get_report_detail("task-1")
        assert result is not None
        assert "Review task diagnostics" in result.next_actions

    def test_clean_release_enriches_detail(self):
        from src.core.task_manager.models import TaskStatus
        from src.models.report import TaskType
        # Create a task that maps to CLEAN_RELEASE type
        tm = MagicMock()
        task = _make_task(id="cr-1", plugin_id="clean-release-compliance",
                          result={"run_id": "run-1"})
        tm.get_all_tasks.return_value = [task]

        crr = MagicMock()
        mock_run = MagicMock()
        mock_run.id = "run-1"
        mock_run.candidate_id = "cand-1"
        mock_run.status = "completed"
        mock_run.final_status = "approved"
        mock_run.requested_by = "admin"
        crr.get_check_run.return_value = mock_run
        crr.reports = {}

        service = _make_service(task_manager=tm, clean_release_repo=crr)
        result = service.get_report_detail("cr-1")
        assert result is not None
        assert "clean_release_run" in result.diagnostics

    def test_clean_release_report_linked(self):
        tm = MagicMock()
        task = _make_task(id="cr-2", plugin_id="clean-release-compliance",
                          result={"run_id": "run-2"})
        tm.get_all_tasks.return_value = [task]

        crr = MagicMock()
        mock_run = MagicMock()
        mock_run.id = "run-2"
        mock_run.candidate_id = "cand-2"
        mock_run.status = "completed"
        mock_run.final_status = "approved"
        mock_run.requested_by = "admin"
        crr.get_check_run.return_value = mock_run

        mock_report = MagicMock()
        mock_report.id = "report-1"
        mock_report.final_status = "compliant"
        crr.reports = {"rep1": mock_report}

        # Make the iteration find the report by run_id
        mock_report.run_id = "run-2"

        service = _make_service(task_manager=tm, clean_release_repo=crr)
        result = service.get_report_detail("cr-2")
        assert result is not None
        assert "clean_release_report" in result.diagnostics

    def test_diagnostics_not_provided(self):
        tm = MagicMock()
        task = _make_task(id="task-1", result=None)
        tm.get_all_tasks.return_value = [task]
        service = _make_service(task_manager=tm)
        result = service.get_report_detail("task-1")
        assert result is not None
        assert result.diagnostics is not None

    def test_diagnostics_empty_details_uses_fallback(self):
        """When details is empty/falsy, uses 'Not provided' fallback (line 247)."""
        from src.models.report import TaskReport, ReportStatus, TaskType
        from unittest.mock import PropertyMock
        from datetime import UTC, datetime

        tm = MagicMock()
        tm.get_all_tasks.return_value = []
        service = _make_service(task_manager=tm)

        # Mock _load_normalized_reports to return a report with empty details
        mock_report = MagicMock(spec=TaskReport)
        mock_report.report_id = "empty-details-task"
        mock_report.task_type = TaskType.MIGRATION
        mock_report.status = ReportStatus.SUCCESS
        mock_report.updated_at = datetime(2024, 1, 1, tzinfo=UTC)
        mock_report.started_at = None
        mock_report.error_context = None
        type(mock_report).details = PropertyMock(return_value={})

        with patch.object(service, "_load_normalized_reports", return_value=[mock_report]):
            result = service.get_report_detail("empty-details-task")
            assert result is not None
            assert result.diagnostics == {"note": "Not provided"}

    def test_default_next_actions_for_failed_without_error_context_actions(self):
        """When error_context exists but next_actions is empty, uses default (line 290)."""
        from src.models.report import TaskReport, ReportStatus, TaskType, ErrorContext
        from datetime import UTC, datetime

        tm = MagicMock()
        tm.get_all_tasks.return_value = []
        service = _make_service(task_manager=tm)

        mock_report = MagicMock(spec=TaskReport)
        mock_report.report_id = "failed-no-actions"
        mock_report.task_type = TaskType.MIGRATION
        mock_report.status = ReportStatus.FAILED
        mock_report.updated_at = datetime(2024, 1, 1, tzinfo=UTC)
        mock_report.started_at = None
        mock_report.details = {"result": {}}
        mock_report.error_context = ErrorContext(
            message="test error",
            code=None,
            next_actions=[],
        )

        with patch.object(service, "_load_normalized_reports", return_value=[mock_report]):
            result = service.get_report_detail("failed-no-actions")
            assert result is not None
            assert "Review diagnostics" in result.next_actions
            assert "Retry task if applicable" in result.next_actions
# #endregion test_get_report_detail
# #endregion Test.ReportsService
