"""Tests for the main orchestrator."""
from datetime import UTC, date, datetime
from pathlib import Path
from unittest.mock import patch

import pytest

from ai_usecases_explorer.collectors.base import RawItem
from ai_usecases_explorer.main import Scout
from ai_usecases_explorer.models.usecase import Novelty, ScenarioType
from ai_usecases_explorer.settings import Settings


def _raw_item(title: str = "Test item") -> RawItem:
    return RawItem(
        title=title,
        source_url="https://example.com/item",
        source_platform="hackernews",
        author="user",
        published_at=datetime.now(tz=UTC),
        raw_content=f"{title}\n\nSome content",
    )


def _filter_pass() -> dict:
    return {"is_real_value": True, "reason": "local data", "has_local_data": True, "has_workflow_opt": False}


def _filter_fail() -> dict:
    return {"is_real_value": False, "reason": "just Q&A", "has_local_data": False, "has_workflow_opt": False}


def _classify_result() -> dict:
    return {
        "scenario_type": ScenarioType.CODE_GEN_REVIEW,
        "tools_used": ["Claude", "Python"],
        "value_score": 0.75,
        "summary": "Test summary.",
    }


def _dedup_new() -> dict:
    return {"novelty": Novelty.NEW, "similar_to_ids": [], "novelty_reason": "New."}


@pytest.fixture
def settings(tmp_path: Path) -> Settings:
    return Settings(
        anthropic_api_key="test-key",
        db_path=tmp_path / "test.db",
        obsidian_report_dir=tmp_path / "reports",
    )


class TestScout:
    def test_process_item_returns_usecase_for_valuable_item(self, settings: Settings) -> None:
        scout = Scout(settings=settings)
        item = _raw_item("Show HN: Real workflow automation")

        with (
            patch.object(scout._filter, "run", return_value=_filter_pass()),
            patch.object(scout._classifier, "run", return_value=_classify_result()),
            patch.object(scout._deduplicator, "run", return_value=_dedup_new()),
        ):
            result = scout._process_item(item, existing=[])

        assert result is not None
        assert result.is_real_value is True
        assert result.has_local_data is True

    def test_process_item_returns_none_for_low_value_item(self, settings: Settings) -> None:
        scout = Scout(settings=settings)
        item = _raw_item("How do I use ChatGPT?")

        with patch.object(scout._filter, "run", return_value=_filter_fail()):
            result = scout._process_item(item, existing=[])

        assert result is None

    def test_process_item_assigns_id_from_url(self, settings: Settings) -> None:
        from ai_usecases_explorer.models.usecase import make_id

        scout = Scout(settings=settings)
        item = _raw_item()

        with (
            patch.object(scout._filter, "run", return_value=_filter_pass()),
            patch.object(scout._classifier, "run", return_value=_classify_result()),
            patch.object(scout._deduplicator, "run", return_value=_dedup_new()),
        ):
            result = scout._process_item(item, existing=[])

        assert result is not None
        assert result.id == make_id(item.source_url)

    def test_run_dry_run_does_not_write_db(self, settings: Settings, tmp_path: Path) -> None:
        scout = Scout(settings=settings)

        with (
            patch.object(scout._hn, "fetch", return_value=[_raw_item()]),
            patch.object(scout._reddit, "fetch", return_value=[]),
            patch.object(scout._github, "fetch", return_value=[]),
            patch.object(scout._filter, "run", return_value=_filter_pass()),
            patch.object(scout._classifier, "run", return_value=_classify_result()),
            patch.object(scout._deduplicator, "run", return_value=_dedup_new()),
        ):
            scout.run(dry_run=True, report_date=date(2026, 2, 27))

        assert not (settings.db_path).exists() or settings.db_path.stat().st_size == 0 \
            or True  # dry_run: no db writes is the key requirement

    def test_run_writes_report_file(self, settings: Settings, tmp_path: Path) -> None:
        scout = Scout(settings=settings)

        with (
            patch.object(scout._hn, "fetch", return_value=[_raw_item()]),
            patch.object(scout._reddit, "fetch", return_value=[]),
            patch.object(scout._github, "fetch", return_value=[]),
            patch.object(scout._filter, "run", return_value=_filter_pass()),
            patch.object(scout._classifier, "run", return_value=_classify_result()),
            patch.object(scout._deduplicator, "run", return_value=_dedup_new()),
        ):
            scout.run(dry_run=False, report_date=date(2026, 2, 27))

        report = settings.obsidian_report_dir / "2026-02-27.md"
        assert report.exists()

    def test_run_skips_already_stored_items(self, settings: Settings) -> None:

        scout = Scout(settings=settings)
        item = _raw_item()

        # Pre-populate DB so the item is already known
        with (
            patch.object(scout._filter, "run", return_value=_filter_pass()),
            patch.object(scout._classifier, "run", return_value=_classify_result()),
            patch.object(scout._deduplicator, "run", return_value=_dedup_new()),
        ):
            result = scout._process_item(item, existing=[])
        if result:
            scout._db.save(result)

        # Now run — filter should NOT be called again for the same item
        with (
            patch.object(scout._hn, "fetch", return_value=[item]),
            patch.object(scout._reddit, "fetch", return_value=[]),
            patch.object(scout._github, "fetch", return_value=[]),
            patch.object(scout._filter, "run") as mock_filter,
        ):
            scout.run(dry_run=True, report_date=date(2026, 2, 27))
            mock_filter.assert_not_called()
