# tests/tasks/test_cost_tasks.py
"""
Tests for cost tracking and budget management tasks.

These tests verify:
- Task signatures are correctly defined
- Tasks are bound (have access to self)
- Tasks return expected structure
- Tasks follow ADR-012 (multi-layered cost tracking)

Note: These tests mock actual execution since they would require
database access and Redis operations in production.
"""

import uuid
from unittest.mock import patch


class TestAggregateDailyCostsTask:
    """Tests for the aggregate_daily_costs task."""

    def test_aggregate_daily_costs_task_exists(self):
        """Test that aggregate_daily_costs task is registered."""
        import app.tasks.cost  # noqa: F401
        from app.celery_app import celery_app

        assert "app.tasks.cost.aggregate_daily_costs" in celery_app.tasks

    def test_aggregate_daily_costs_is_bound_task(self):
        """Test that aggregate_daily_costs is a bound task."""
        from app.tasks.cost import aggregate_daily_costs

        assert aggregate_daily_costs.__bound__ is True

    def test_aggregate_daily_costs_has_correct_name(self):
        """Test that aggregate_daily_costs has the correct task name."""
        from app.tasks.cost import aggregate_daily_costs

        assert aggregate_daily_costs.name == "app.tasks.cost.aggregate_daily_costs"

    def test_aggregate_daily_costs_returns_expected_structure(self):
        """Test that aggregate_daily_costs returns expected result."""
        from app.tasks.cost import aggregate_daily_costs

        result = aggregate_daily_costs()

        assert isinstance(result, dict)
        assert "status" in result
        assert result["status"] == "pending"


class TestCheckBudgetThresholdsTask:
    """Tests for the check_budget_thresholds task."""

    def test_check_budget_thresholds_task_exists(self):
        """Test that check_budget_thresholds task is registered."""
        import app.tasks.cost  # noqa: F401
        from app.celery_app import celery_app

        assert "app.tasks.cost.check_budget_thresholds" in celery_app.tasks

    def test_check_budget_thresholds_is_bound_task(self):
        """Test that check_budget_thresholds is a bound task."""
        from app.tasks.cost import check_budget_thresholds

        assert check_budget_thresholds.__bound__ is True

    def test_check_budget_thresholds_returns_expected_structure(self):
        """Test that check_budget_thresholds returns expected result."""
        from app.tasks.cost import check_budget_thresholds

        project_id = str(uuid.uuid4())

        result = check_budget_thresholds(project_id)

        assert isinstance(result, dict)
        assert "status" in result
        assert "project_id" in result
        assert result["project_id"] == project_id


class TestRecordLlmUsageTask:
    """Tests for the record_llm_usage task."""

    def test_record_llm_usage_task_exists(self):
        """Test that record_llm_usage task is registered."""
        import app.tasks.cost  # noqa: F401
        from app.celery_app import celery_app

        assert "app.tasks.cost.record_llm_usage" in celery_app.tasks

    def test_record_llm_usage_is_bound_task(self):
        """Test that record_llm_usage is a bound task."""
        from app.tasks.cost import record_llm_usage

        assert record_llm_usage.__bound__ is True

    def test_record_llm_usage_returns_expected_structure(self):
        """Test that record_llm_usage returns expected result."""
        from app.tasks.cost import record_llm_usage

        agent_id = str(uuid.uuid4())
        project_id = str(uuid.uuid4())
        model = "claude-opus-4-5-20251101"
        prompt_tokens = 1500
        completion_tokens = 500
        cost_usd = 0.0825

        result = record_llm_usage(
            agent_id, project_id, model, prompt_tokens, completion_tokens, cost_usd
        )

        assert isinstance(result, dict)
        assert "status" in result
        assert "agent_id" in result
        assert "project_id" in result
        assert "cost_usd" in result
        assert result["agent_id"] == agent_id
        assert result["project_id"] == project_id
        assert result["cost_usd"] == cost_usd

    def test_record_llm_usage_with_different_models(self):
        """Test that record_llm_usage handles different model types."""
        from app.tasks.cost import record_llm_usage

        agent_id = str(uuid.uuid4())
        project_id = str(uuid.uuid4())

        models = [
            ("claude-opus-4-5-20251101", 0.015),
            ("claude-sonnet-4-20250514", 0.003),
            ("gpt-4-turbo", 0.01),
            ("gemini-1.5-pro", 0.007),
        ]

        for model, cost in models:
            result = record_llm_usage(
                agent_id, project_id, model, 1000, 500, cost
            )
            assert result["status"] == "pending"

    def test_record_llm_usage_with_zero_tokens(self):
        """Test that record_llm_usage handles zero token counts."""
        from app.tasks.cost import record_llm_usage

        agent_id = str(uuid.uuid4())
        project_id = str(uuid.uuid4())

        result = record_llm_usage(
            agent_id, project_id, "claude-opus-4-5-20251101", 0, 0, 0.0
        )

        assert result["status"] == "pending"


class TestGenerateCostReportTask:
    """Tests for the generate_cost_report task."""

    def test_generate_cost_report_task_exists(self):
        """Test that generate_cost_report task is registered."""
        import app.tasks.cost  # noqa: F401
        from app.celery_app import celery_app

        assert "app.tasks.cost.generate_cost_report" in celery_app.tasks

    def test_generate_cost_report_is_bound_task(self):
        """Test that generate_cost_report is a bound task."""
        from app.tasks.cost import generate_cost_report

        assert generate_cost_report.__bound__ is True

    def test_generate_cost_report_returns_expected_structure(self):
        """Test that generate_cost_report returns expected result."""
        from app.tasks.cost import generate_cost_report

        project_id = str(uuid.uuid4())
        start_date = "2025-01-01"
        end_date = "2025-01-31"

        result = generate_cost_report(project_id, start_date, end_date)

        assert isinstance(result, dict)
        assert "status" in result
        assert "project_id" in result
        assert "start_date" in result
        assert "end_date" in result
        assert result["project_id"] == project_id
        assert result["start_date"] == start_date
        assert result["end_date"] == end_date

    def test_generate_cost_report_with_various_date_ranges(self):
        """Test that generate_cost_report handles various date ranges."""
        from app.tasks.cost import generate_cost_report

        project_id = str(uuid.uuid4())

        date_ranges = [
            ("2025-01-01", "2025-01-01"),  # Single day
            ("2025-01-01", "2025-01-07"),  # Week
            ("2025-01-01", "2025-12-31"),  # Full year
        ]

        for start, end in date_ranges:
            result = generate_cost_report(project_id, start, end)
            assert result["status"] == "pending"


class TestResetDailyBudgetCountersTask:
    """Tests for the reset_daily_budget_counters task."""

    def test_reset_daily_budget_counters_task_exists(self):
        """Test that reset_daily_budget_counters task is registered."""
        import app.tasks.cost  # noqa: F401
        from app.celery_app import celery_app

        assert "app.tasks.cost.reset_daily_budget_counters" in celery_app.tasks

    def test_reset_daily_budget_counters_is_bound_task(self):
        """Test that reset_daily_budget_counters is a bound task."""
        from app.tasks.cost import reset_daily_budget_counters

        assert reset_daily_budget_counters.__bound__ is True

    def test_reset_daily_budget_counters_returns_expected_structure(self):
        """Test that reset_daily_budget_counters returns expected result."""
        from app.tasks.cost import reset_daily_budget_counters

        result = reset_daily_budget_counters()

        assert isinstance(result, dict)
        assert "status" in result
        assert result["status"] == "pending"


class TestCostTaskRouting:
    """Tests for cost task queue routing."""

    def test_cost_tasks_route_to_default_queue(self):
        """Test that cost tasks route to 'default' queue.

        Per the routing configuration, cost tasks match 'app.tasks.*'
        which routes to the default queue.
        """
        from app.celery_app import celery_app

        routes = celery_app.conf.task_routes

        # Cost tasks match the generic 'app.tasks.*' pattern
        assert "app.tasks.*" in routes
        assert routes["app.tasks.*"]["queue"] == "default"

    def test_all_cost_tasks_match_routing_pattern(self):
        """Test that all cost task names match the routing pattern."""
        task_names = [
            "app.tasks.cost.aggregate_daily_costs",
            "app.tasks.cost.check_budget_thresholds",
            "app.tasks.cost.record_llm_usage",
            "app.tasks.cost.generate_cost_report",
            "app.tasks.cost.reset_daily_budget_counters",
        ]

        for name in task_names:
            assert name.startswith("app.tasks.")


class TestCostTaskLogging:
    """Tests for cost task logging behavior."""

    def test_aggregate_daily_costs_logs_execution(self):
        """Test that aggregate_daily_costs logs when executed."""
        from app.tasks.cost import aggregate_daily_costs

        with patch("app.tasks.cost.logger") as mock_logger:
            aggregate_daily_costs()

            mock_logger.info.assert_called_once()
            call_args = mock_logger.info.call_args[0][0]
            assert "cost" in call_args.lower() or "aggregat" in call_args.lower()

    def test_check_budget_thresholds_logs_execution(self):
        """Test that check_budget_thresholds logs when executed."""
        from app.tasks.cost import check_budget_thresholds

        project_id = str(uuid.uuid4())

        with patch("app.tasks.cost.logger") as mock_logger:
            check_budget_thresholds(project_id)

            mock_logger.info.assert_called_once()
            call_args = mock_logger.info.call_args[0][0]
            assert project_id in call_args

    def test_record_llm_usage_logs_execution(self):
        """Test that record_llm_usage logs when executed."""
        from app.tasks.cost import record_llm_usage

        agent_id = str(uuid.uuid4())
        project_id = str(uuid.uuid4())
        model = "claude-opus-4-5-20251101"

        with patch("app.tasks.cost.logger") as mock_logger:
            record_llm_usage(agent_id, project_id, model, 100, 50, 0.01)

            # Uses debug level, not info
            mock_logger.debug.assert_called_once()
            call_args = mock_logger.debug.call_args[0][0]
            assert model in call_args

    def test_generate_cost_report_logs_execution(self):
        """Test that generate_cost_report logs when executed."""
        from app.tasks.cost import generate_cost_report

        project_id = str(uuid.uuid4())

        with patch("app.tasks.cost.logger") as mock_logger:
            generate_cost_report(project_id, "2025-01-01", "2025-01-31")

            mock_logger.info.assert_called_once()
            call_args = mock_logger.info.call_args[0][0]
            assert project_id in call_args

    def test_reset_daily_budget_counters_logs_execution(self):
        """Test that reset_daily_budget_counters logs when executed."""
        from app.tasks.cost import reset_daily_budget_counters

        with patch("app.tasks.cost.logger") as mock_logger:
            reset_daily_budget_counters()

            mock_logger.info.assert_called_once()
            call_args = mock_logger.info.call_args[0][0]
            assert "reset" in call_args.lower() or "counter" in call_args.lower()


class TestCostTaskSignatures:
    """Tests for cost task signature creation."""

    def test_record_llm_usage_signature_creation(self):
        """Test that record_llm_usage signature can be created."""
        from app.tasks.cost import record_llm_usage

        agent_id = str(uuid.uuid4())
        project_id = str(uuid.uuid4())

        sig = record_llm_usage.s(
            agent_id, project_id, "claude-opus-4-5-20251101", 100, 50, 0.01
        )

        assert sig is not None
        assert len(sig.args) == 6

    def test_check_budget_thresholds_signature_creation(self):
        """Test that check_budget_thresholds signature can be created."""
        from app.tasks.cost import check_budget_thresholds

        project_id = str(uuid.uuid4())

        sig = check_budget_thresholds.s(project_id)

        assert sig is not None
        assert sig.args == (project_id,)

    def test_cost_task_chain_creation(self):
        """Test that cost tasks can be chained together."""
        from celery import chain

        from app.tasks.cost import check_budget_thresholds, record_llm_usage

        agent_id = str(uuid.uuid4())
        project_id = str(uuid.uuid4())

        # Build a chain: record usage, then check thresholds
        workflow = chain(
            record_llm_usage.s(
                agent_id, project_id, "claude-opus-4-5-20251101", 1000, 500, 0.05
            ),
            check_budget_thresholds.s(project_id),
        )

        assert workflow is not None
