"""Tests for scheduler polling logic."""
from datetime import datetime, timedelta

from services.scheduler.app import (
    DEFAULT_CADENCES,
    MAX_RETRY_COUNT,
    build_job_payload,
    compute_backoff,
    get_cadence_for_source,
    is_source_due,
)


class TestGetCadenceForSource:
    def test_default_cadence_market_api(self):
        assert get_cadence_for_source("market_api", None) == 300

    def test_default_cadence_news_api(self):
        assert get_cadence_for_source("news_api", {}) == 300

    def test_default_cadence_unknown_type(self):
        assert get_cadence_for_source("unknown", None) == 600

    def test_override_from_config(self):
        config = {"polling_interval_seconds": 120}
        assert get_cadence_for_source("market_api", config) == 120

    def test_override_minimum_clamp(self):
        config = {"polling_interval_seconds": 5}
        assert get_cadence_for_source("market_api", config) == 10

    def test_invalid_override_falls_back(self):
        config = {"polling_interval_seconds": "not_a_number"}
        assert get_cadence_for_source("news_api", config) == DEFAULT_CADENCES["news_api"]


class TestComputeBackoff:
    def test_first_retry(self):
        assert compute_backoff(0) == 60

    def test_second_retry(self):
        assert compute_backoff(1) == 120

    def test_capped_at_max(self):
        assert compute_backoff(20) == 3600


class TestIsSourceDue:
    def _now(self):
        return datetime(2026, 4, 11, 12, 0, 0)

    def test_never_run_is_due(self):
        assert is_source_due("market_api", None, None, None, 0, None, self._now())

    def test_completed_within_cadence_not_due(self):
        last = self._now() - timedelta(seconds=30)
        assert not is_source_due("market_api", None, last, "completed", 0, None, self._now())

    def test_completed_past_cadence_is_due(self):
        last = self._now() - timedelta(seconds=400)
        assert is_source_due("market_api", None, last, "completed", 0, None, self._now())

    def test_running_not_due(self):
        last = self._now() - timedelta(seconds=5)
        assert not is_source_due("market_api", None, last, "running", 0, None, self._now())

    def test_failed_within_backoff_not_due(self):
        last = self._now() - timedelta(seconds=30)
        next_retry = self._now() + timedelta(seconds=30)
        assert not is_source_due("market_api", None, last, "failed", 1, next_retry, self._now())

    def test_failed_past_backoff_is_due(self):
        last = self._now() - timedelta(seconds=120)
        next_retry = self._now() - timedelta(seconds=10)
        assert is_source_due("market_api", None, last, "failed", 1, next_retry, self._now())

    def test_failed_max_retries_not_due(self):
        last = self._now() - timedelta(seconds=120)
        assert not is_source_due(
            "market_api", None, last, "failed", MAX_RETRY_COUNT, None, self._now()
        )

    def test_custom_cadence_respected(self):
        config = {"polling_interval_seconds": 600}
        last = self._now() - timedelta(seconds=300)
        assert not is_source_due("market_api", config, last, "completed", 0, None, self._now())

        last_old = self._now() - timedelta(seconds=700)
        assert is_source_due("market_api", config, last_old, "completed", 0, None, self._now())


class TestBuildJobPayload:
    def test_payload_structure(self):
        source = {
            "source_id": "sid-1",
            "company_id": "cid-1",
            "ticker": "AAPL",
            "legal_name": "Apple Inc.",
            "source_type": "news_api",
            "source_name": "NewsAPI",
            "config": {"endpoint": "/v2/everything"},
            "credibility_score": 0.8,
        }
        now = datetime(2026, 4, 11, 12, 0, 0)
        job = build_job_payload(source, ["Apple", "iPhone"], now)

        assert job["source_id"] == "sid-1"
        assert job["company_id"] == "cid-1"
        assert job["ticker"] == "AAPL"
        assert job["legal_name"] == "Apple Inc."
        assert job["aliases"] == ["Apple", "iPhone"]
        assert job["source_type"] == "news_api"
        assert job["config"] == {"endpoint": "/v2/everything"}
        assert job["credibility_score"] == 0.8
        assert job["scheduled_at"] == now.isoformat()

    def test_payload_null_config(self):
        source = {
            "source_id": "sid-2",
            "company_id": "cid-2",
            "ticker": "MSFT",
            "legal_name": "Microsoft Corp.",
            "source_type": "market_api",
            "source_name": "Polygon",
            "config": None,
            "credibility_score": None,
        }
        job = build_job_payload(source, [], datetime(2026, 4, 11, 12, 0, 0))
        assert job["config"] == {}
        assert job["credibility_score"] == 0.5
        assert job["aliases"] == []
