"""Tests for diarization job lifecycle improvements.

Sprint GAP-004: Diarization Job Lifecycle Issues.

Tests cover:
- Database requirement enforcement for diarization jobs
- Error handling when database unavailable
"""

from __future__ import annotations

import grpc
import pytest

from noteflow.grpc._config import ServicesConfig
from noteflow.grpc.proto import noteflow_pb2
from noteflow.grpc.service import NoteFlowServicer
from noteflow.infrastructure.diarization import DiarizationEngine


class _FakeDiarizationEngine(DiarizationEngine):
    """Lightweight diarization engine stub for service config."""

    def __init__(self) -> None:
        super().__init__()


class _AbortCalled(Exception):
    """Exception raised when gRPC abort is called."""

    def __init__(self, code: grpc.StatusCode, details: str) -> None:
        self.code = code
        self.details = details
        super().__init__(f"abort({code}, {details})")


class _MockGrpcContext:
    """Minimal async gRPC context for tests."""

    def __init__(self) -> None:
        """Initialize context."""
        self.abort_code: grpc.StatusCode | None = None
        self.abort_details: str | None = None

    def set_code(self, code: grpc.StatusCode) -> None:
        """Set response status code."""
        self.abort_code = code

    def set_details(self, details: str) -> None:
        """Set response status details."""
        self.abort_details = details

    async def abort(self, code: grpc.StatusCode, details: str) -> None:
        """Simulate gRPC abort by raising exception."""
        self.abort_code = code
        self.abort_details = details
        raise _AbortCalled(code, details)


@pytest.fixture
def servicer() -> NoteFlowServicer:
    """Create servicer with mock diarization engine but no DB support."""
    return NoteFlowServicer(services=ServicesConfig(diarization_engine=_FakeDiarizationEngine()))


class TestDatabaseRequirement:
    """Tests verifying diarization requires database support."""

    @pytest.mark.asyncio
    async def test_refine_requires_database_support(self, servicer: NoteFlowServicer) -> None:
        """RefineSpeakerDiarization returns error when database unavailable."""
        store = servicer.get_memory_store()
        meeting = store.create("Test meeting")
        meeting.start_recording()
        meeting.begin_stopping()
        meeting.stop_recording()
        store.update(meeting)

        context = _MockGrpcContext()
        response = await servicer.RefineSpeakerDiarization(
            noteflow_pb2.RefineSpeakerDiarizationRequest(meeting_id=str(meeting.id)),
            context,
        )

        # Should return error response, not job_id
        assert not response.job_id, "Should not return job_id without database"
        assert response.status == noteflow_pb2.JOB_STATUS_FAILED, "Status should be FAILED"
        assert context.abort_code == grpc.StatusCode.FAILED_PRECONDITION, "Error code should be FAILED_PRECONDITION"

    @pytest.mark.asyncio
    async def test_refine_error_mentions_database(self, servicer: NoteFlowServicer) -> None:
        """Error message should mention database requirement."""
        store = servicer.get_memory_store()
        meeting = store.create("Test meeting")
        meeting.start_recording()
        meeting.begin_stopping()
        meeting.stop_recording()
        store.update(meeting)

        context = _MockGrpcContext()
        response = await servicer.RefineSpeakerDiarization(
            noteflow_pb2.RefineSpeakerDiarizationRequest(meeting_id=str(meeting.id)),
            context,
        )

        # Error message should explain database is required
        error_msg = response.error_message.lower()
        assert "database" in error_msg, "Error should mention 'database'"

    @pytest.mark.asyncio
    async def test_get_status_requires_database(self, servicer: NoteFlowServicer) -> None:
        """GetDiarizationJobStatus aborts when database unavailable."""
        context = _MockGrpcContext()

        # Should abort because no DB support
        with pytest.raises(_AbortCalled, match="database") as exc_info:
            await servicer.GetDiarizationJobStatus(
                noteflow_pb2.GetDiarizationJobStatusRequest(job_id="any-job-id"),
                context,
            )

        # Verify abort was called with NOT_FOUND (no DB support means jobs can't be found)
        assert exc_info.value.code == grpc.StatusCode.NOT_FOUND, "Should abort with NOT_FOUND"
        assert "database" in exc_info.value.details.lower(), "Error should mention database"

    @pytest.mark.asyncio
    async def test_cancel_requires_database(self, servicer: NoteFlowServicer) -> None:
        """CancelDiarizationJob returns error when database unavailable."""
        context = _MockGrpcContext()

        response = await servicer.CancelDiarizationJob(
            noteflow_pb2.CancelDiarizationJobRequest(job_id="any-job-id"),
            context,
        )

        assert response.success is False, "Cancel should fail without database"
        assert "database" in response.error_message.lower(), "Error should mention database"
