from datetime import datetime, timezone

from src.models import ContentPipeline, Message, PipelineGenerationBackend, PipelinePublishMode, SearchResult
from src.services.content_generation_service import ContentGenerationService


class DummySearchEngine:
    def __init__(self, messages):
        self._messages = messages

    async def search_hybrid(self, query: str, **kwargs) -> SearchResult:
        return SearchResult(messages=self._messages, total=len(self._messages), query=query)


async def fake_provider(**kwargs):
    return "GENERATED: " + (kwargs.get("prompt") or "")[:40]


class FakeGenerationRunsRepo:
    def __init__(self):
        self._runs = {}
        self._next_id = 1

    async def create_run(self, pipeline_id, prompt):
        from src.models import GenerationRun
        run_id = self._next_id
        self._next_id += 1
        run = GenerationRun(
            id=run_id,
            pipeline_id=pipeline_id,
            prompt=prompt,
            status="pending",
            generated_text=None,
            metadata=None,
            moderation_status="pending",
        )
        self._runs[run_id] = run
        return run_id

    async def set_status(self, run_id, status):
        if run_id in self._runs:
            self._runs[run_id].status = status

    async def save_result(self, run_id, generated_text, metadata=None):
        if run_id in self._runs:
            self._runs[run_id].generated_text = generated_text
            self._runs[run_id].metadata = metadata

    async def get(self, run_id):
        return self._runs.get(run_id)

    async def set_quality_score(self, run_id, score, issues=None):
        if run_id in self._runs:
            self._runs[run_id].quality_score = score
            self._runs[run_id].quality_issues = issues


class FakeRepos:
    def __init__(self):
        self.generation_runs = FakeGenerationRunsRepo()


class FakeDB:
    def __init__(self):
        self.repos = FakeRepos()

    async def execute(self, sql, params=()):
        return None


async def test_content_generation_service_rag():
    msg = Message(
        id=1,
        channel_id=10,
        message_id=42,
        sender_id=None,
        sender_name="Alice",
        text="Hello world from test",
        date=datetime.now(timezone.utc),
        collected_at=None,
        channel_title="TestChannel",
        channel_username="testchan",
    )

    engine = DummySearchEngine([msg])
    db = FakeDB()

    service = ContentGenerationService(db, engine)

    pipeline = ContentPipeline(
        id=1,
        name="Test Pipeline",
        prompt_template="Use {source_messages}",
        llm_model="test-model",
        generation_backend=PipelineGenerationBackend.CHAIN,
        publish_mode=PipelinePublishMode.MODERATED,
    )

    from src.services import provider_service
    original_get = getattr(provider_service.AgentProviderService, "get_provider_callable", None)
    provider_service.AgentProviderService.get_provider_callable = lambda self, model: fake_provider
    try:
        run = await service.generate(pipeline)
        assert run is not None
        assert "GENERATED:" in (run.generated_text or "")
    finally:
        if original_get:
            provider_service.AgentProviderService.get_provider_callable = original_get


async def test_content_generation_service_deep_agents_stub():
    msg = Message(
        id=1,
        channel_id=10,
        message_id=42,
        sender_id=None,
        sender_name="Alice",
        text="Hello world from test",
        date=datetime.now(timezone.utc),
        collected_at=None,
        channel_title="TestChannel",
        channel_username="testchan",
    )

    engine = DummySearchEngine([msg])
    db = FakeDB()

    service = ContentGenerationService(db, engine)

    pipeline = ContentPipeline(
        id=1,
        name="Test Pipeline",
        prompt_template="Use {source_messages}",
        llm_model="test-model",
        generation_backend=PipelineGenerationBackend.DEEP_AGENTS,
        publish_mode=PipelinePublishMode.MODERATED,
    )

    from src.services import provider_service
    original_get = getattr(provider_service.AgentProviderService, "get_provider_callable", None)
    provider_service.AgentProviderService.get_provider_callable = lambda self, model: fake_provider
    try:
        try:
            await service.generate(pipeline)
            assert False, "Expected RuntimeError"
        except RuntimeError as e:
            assert "AgentManager" in str(e) or "not configured" in str(e)
    finally:
        if original_get:
            provider_service.AgentProviderService.get_provider_callable = original_get


async def test_content_generation_service_skips_image_generation_without_service():
    msg = Message(
        id=1,
        channel_id=10,
        message_id=42,
        sender_id=None,
        sender_name="Alice",
        text="Hello world from test",
        date=datetime.now(timezone.utc),
        collected_at=None,
        channel_title="TestChannel",
        channel_username="testchan",
    )

    engine = DummySearchEngine([msg])
    db = FakeDB()
    service = ContentGenerationService(db, engine)

    pipeline = ContentPipeline(
        id=1,
        name="Test Pipeline",
        prompt_template="Use {source_messages}",
        llm_model="test-model",
        image_model="stub-image-model",
        generation_backend=PipelineGenerationBackend.CHAIN,
        publish_mode=PipelinePublishMode.MODERATED,
    )

    from src.services import provider_service

    original_get = getattr(provider_service.AgentProviderService, "get_provider_callable", None)
    provider_service.AgentProviderService.get_provider_callable = lambda self, model: fake_provider
    try:
        run = await service.generate(pipeline)
        assert run is not None
        assert "GENERATED:" in (run.generated_text or "")
    finally:
        if original_get:
            provider_service.AgentProviderService.get_provider_callable = original_get


class FakeDraftNotificationService:
    def __init__(self):
        self.calls = []

    async def notify_new_draft(self, run, pipeline):
        self.calls.append((run, pipeline))
        return True


class FakeQualityService:
    def __init__(self, overall=0.81, issues=None):
        self.overall = overall
        self.issues = issues or ["weak hook"]
        self.calls = []

    async def score_content(self, text, model=None):
        from src.services.quality_scoring_service import QualityScore

        self.calls.append((text, model))
        return QualityScore(
            relevance=self.overall,
            language_quality=self.overall,
            informativeness=self.overall,
            structure=self.overall,
            overall=self.overall,
            issues=self.issues,
        )


async def test_content_generation_service_notifies_for_moderated_drafts():
    msg = Message(
        id=1,
        channel_id=10,
        message_id=42,
        sender_id=None,
        sender_name="Alice",
        text="Hello world from test",
        date=datetime.now(timezone.utc),
        collected_at=None,
        channel_title="TestChannel",
        channel_username="testchan",
    )

    engine = DummySearchEngine([msg])
    db = FakeDB()
    notifications = FakeDraftNotificationService()
    service = ContentGenerationService(db, engine, notification_service=notifications)

    pipeline = ContentPipeline(
        id=1,
        name="Test Pipeline",
        prompt_template="Use {source_messages}",
        llm_model="test-model",
        generation_backend=PipelineGenerationBackend.CHAIN,
        publish_mode=PipelinePublishMode.MODERATED,
    )

    from src.services import provider_service

    original_get = getattr(provider_service.AgentProviderService, "get_provider_callable", None)
    provider_service.AgentProviderService.get_provider_callable = lambda self, model: fake_provider
    try:
        run = await service.generate(pipeline)
        assert run is not None
        assert len(notifications.calls) == 1
        assert notifications.calls[0][0].id == run.id
    finally:
        if original_get:
            provider_service.AgentProviderService.get_provider_callable = original_get


async def test_content_generation_service_skips_notification_for_auto_publish():
    msg = Message(
        id=1,
        channel_id=10,
        message_id=42,
        sender_id=None,
        sender_name="Alice",
        text="Hello world from test",
        date=datetime.now(timezone.utc),
        collected_at=None,
        channel_title="TestChannel",
        channel_username="testchan",
    )

    engine = DummySearchEngine([msg])
    db = FakeDB()
    notifications = FakeDraftNotificationService()
    service = ContentGenerationService(db, engine, notification_service=notifications)

    pipeline = ContentPipeline(
        id=1,
        name="Test Pipeline",
        prompt_template="Use {source_messages}",
        llm_model="test-model",
        generation_backend=PipelineGenerationBackend.CHAIN,
        publish_mode=PipelinePublishMode.AUTO,
    )

    from src.services import provider_service

    original_get = getattr(provider_service.AgentProviderService, "get_provider_callable", None)
    provider_service.AgentProviderService.get_provider_callable = lambda self, model: fake_provider
    try:
        await service.generate(pipeline)
        assert notifications.calls == []
    finally:
        if original_get:
            provider_service.AgentProviderService.get_provider_callable = original_get


async def test_content_generation_service_records_quality_score():
    msg = Message(
        id=1,
        channel_id=10,
        message_id=42,
        sender_id=None,
        sender_name="Alice",
        text="Hello world from test",
        date=datetime.now(timezone.utc),
        collected_at=None,
        channel_title="TestChannel",
        channel_username="testchan",
    )

    engine = DummySearchEngine([msg])
    db = FakeDB()
    quality = FakeQualityService(overall=0.91, issues=["missing CTA"])
    service = ContentGenerationService(db, engine, quality_service=quality)

    pipeline = ContentPipeline(
        id=1,
        name="Test Pipeline",
        prompt_template="Use {source_messages}",
        llm_model="test-model",
        generation_backend=PipelineGenerationBackend.CHAIN,
        publish_mode=PipelinePublishMode.MODERATED,
    )

    from src.services import provider_service

    original_get = getattr(provider_service.AgentProviderService, "get_provider_callable", None)
    provider_service.AgentProviderService.get_provider_callable = lambda self, model: fake_provider
    try:
        run = await service.generate(pipeline)
        assert run.quality_score == 0.91
        assert run.quality_issues == ["missing CTA"]
        assert quality.calls == [(run.generated_text, "test-model")]
    finally:
        if original_get:
            provider_service.AgentProviderService.get_provider_callable = original_get
