"""Server startup helper functions.

Extracted from server.py to reduce module size while maintaining
clean separation of concerns for server initialization.
"""

from __future__ import annotations

import sys
from typing import Protocol, TypedDict, cast

from rich.console import Console
from sqlalchemy.ext.asyncio import AsyncEngine, AsyncSession, async_sessionmaker

from noteflow.application.services import RecoveryService
from noteflow.application.services.calendar_service import CalendarService
from noteflow.application.services.ner_service import NerService
from noteflow.application.services.summarization_service import (
    SummarizationMode,
    SummarizationService,
)
from noteflow.application.services.webhook_service import WebhookService
from noteflow.config.constants import (
    PROVIDER_NAME_OPENAI,
    SETTING_CLOUD_CONSENT_GRANTED,
    STATUS_DISABLED,
)
from noteflow.config.settings import (
    Settings,
    get_calendar_settings,
    get_feature_flags,
    get_settings,
)
from noteflow.domain.entities.integration import IntegrationStatus
from noteflow.infrastructure.diarization import DiarizationEngine
from noteflow.infrastructure.logging import get_logger
from noteflow.infrastructure.ner import NerEngine
from noteflow.infrastructure.persistence.database import (
    create_engine_and_session_factory,
    ensure_schema_ready,
)
from noteflow.infrastructure.persistence.unit_of_work import SqlAlchemyUnitOfWork
from noteflow.infrastructure.summarization import CloudBackend, CloudSummarizer
from noteflow.infrastructure.webhooks import WebhookExecutor

# Export functions for testing
__all__ = [
    "auto_enable_cloud_llm",
    "check_calendar_needed_from_db",
]


class DiarizationEngineKwargs(TypedDict, total=False):
    """Type-safe kwargs for DiarizationEngine initialization."""

    device: str
    hf_token: str | None
    streaming_latency: float
    min_speakers: int
    max_speakers: int


class _SummaryConfig(TypedDict, total=False):
    provider: str
    api_key: str
    test_status: str
    model: str


class _AsrConfigLike(Protocol):
    @property
    def model(self) -> str: ...

    @property
    def device(self) -> str: ...

    @property
    def compute_type(self) -> str: ...


class _DiarizationConfigLike(Protocol):
    @property
    def enabled(self) -> bool: ...

    @property
    def hf_token(self) -> str | None: ...

    @property
    def device(self) -> str: ...

    @property
    def streaming_latency(self) -> float | None: ...

    @property
    def min_speakers(self) -> int | None: ...

    @property
    def max_speakers(self) -> int | None: ...


class _GrpcServerConfigLike(Protocol):
    @property
    def port(self) -> int: ...

    @property
    def asr(self) -> _AsrConfigLike: ...

    @property
    def database_url(self) -> str | None: ...

    @property
    def diarization(self) -> _DiarizationConfigLike: ...

logger = get_logger(__name__)


async def auto_enable_cloud_llm(
    uow: SqlAlchemyUnitOfWork,
    summarization_service: SummarizationService,
) -> str | None:
    """Check for app-configured LLM and register cloud provider if valid.

    Args:
        uow: Unit of work with preferences access.
        summarization_service: Service to register cloud provider with.

    Returns:
        Provider name if cloud provider was auto-enabled, None otherwise.
    """
    ai_config_value = await uow.preferences.get("ai_config")
    if not isinstance(ai_config_value, dict):
        return None

    ai_config = cast(dict[str, object], ai_config_value)
    summary_config_value = ai_config.get("summary", {})
    if not isinstance(summary_config_value, dict):
        return None

    summary_config = cast(_SummaryConfig, summary_config_value)
    provider = summary_config.get("provider", "")
    api_key = summary_config.get("api_key", "")
    test_status = summary_config.get("test_status", "")
    model = summary_config.get("model") or None  # Convert empty string to None

    # Only register if configured and tested successfully
    if provider not in (PROVIDER_NAME_OPENAI, "anthropic") or not api_key or test_status != "success":
        return None

    backend = CloudBackend.OPENAI if provider == PROVIDER_NAME_OPENAI else CloudBackend.ANTHROPIC
    cloud_summarizer = CloudSummarizer(
        backend=backend,
        api_key=api_key,
        model=model,
    )
    summarization_service.register_provider(SummarizationMode.CLOUD, cloud_summarizer)
    # Auto-grant consent since user explicitly configured in app
    summarization_service.settings.cloud_consent_granted = True
    logger.info("Auto-enabled CLOUD summarization from app config: provider=%s", provider)
    return provider


async def check_calendar_needed_from_db(uow: SqlAlchemyUnitOfWork) -> bool:
    """Check if calendar should be enabled based on database OAuth connections.

    Args:
        uow: Unit of work with integrations access.

    Returns:
        True if connected calendar integrations exist.
    """
    if not uow.supports_integrations:
        return False

    calendar_integrations = await uow.integrations.list_by_type("calendar")
    if connected := [
        i
        for i in calendar_integrations
        if i.status == IntegrationStatus.CONNECTED
    ]:
        logger.info(
            "Auto-enabling calendar: found %d connected OAuth integration(s)",
            len(connected),
        )
        return True
    return False


async def init_database_and_recovery(
    database_url: str,
) -> tuple[AsyncEngine, async_sessionmaker[AsyncSession]]:
    """Initialize database connection and run crash recovery.

    Args:
        database_url: Database connection URL.

    Returns:
        Tuple of (engine, session_factory).
    """
    logger.info("Connecting to database...")
    db_engine, session_factory = create_engine_and_session_factory(database_url)
    logger.info("Database connection pool ready")

    await ensure_schema_ready(session_factory, database_url)

    settings = get_settings()
    recovery_service = RecoveryService(
        SqlAlchemyUnitOfWork(session_factory, settings.meetings_dir),
        meetings_dir=settings.meetings_dir,
    )
    recovery_result = await recovery_service.recover_all()
    if recovery_result.meetings_recovered:
        logger.warning(
            "Recovered %d crashed meetings on startup",
            recovery_result.meetings_recovered,
        )
    if recovery_result.diarization_jobs_failed:
        logger.warning(
            "Recovered %d crashed diarization jobs on startup",
            recovery_result.diarization_jobs_failed,
        )
    if recovery_result.audio_validation_failures:
        logger.warning(
            "Found %d meetings with missing/invalid audio files",
            recovery_result.audio_validation_failures,
        )
    return db_engine, session_factory


async def setup_summarization_with_consent(
    session_factory: async_sessionmaker[AsyncSession],
    summarization_service: SummarizationService,
    settings: Settings,
) -> str | None:
    """Load cloud consent and configure cloud LLM if available.

    Args:
        session_factory: Database session factory.
        summarization_service: Service to configure.
        settings: Application settings.

    Returns:
        Cloud provider name if auto-enabled, None otherwise.
    """
    async with SqlAlchemyUnitOfWork(session_factory, settings.meetings_dir) as uow:
        cloud_consent = await uow.preferences.get_bool("cloud_consent_granted", False)
        summarization_service.settings.cloud_consent_granted = cloud_consent
        logger.info("Loaded cloud consent from database: %s", cloud_consent)
        cloud_llm_provider = await auto_enable_cloud_llm(uow, summarization_service)

    async def persist_consent(granted: bool) -> None:
        async with SqlAlchemyUnitOfWork(session_factory, settings.meetings_dir) as uow:
            await uow.preferences.set(SETTING_CLOUD_CONSENT_GRANTED, granted)
            await uow.commit()
            logger.info("Persisted cloud consent: %s", granted)

    summarization_service.on_consent_change = persist_consent
    return cloud_llm_provider


def create_ner_service(
    session_factory: async_sessionmaker[AsyncSession] | None,
    settings: Settings,
) -> NerService | None:
    """Create NER service if enabled and database available.

    Args:
        session_factory: Database session factory.
        settings: Application settings.

    Returns:
        NerService if enabled, None otherwise.
    """
    if not get_feature_flags().ner_enabled:
        return None

    if not session_factory:
        logger.warning(
            "NER feature enabled but no database configured. "
            "NER requires database for entity persistence."
        )
        return None

    logger.info("Initializing NER service (spaCy)...")
    ner_engine = NerEngine()
    ner_service = NerService(
        ner_engine=ner_engine,
        uow_factory=lambda: SqlAlchemyUnitOfWork(session_factory, settings.meetings_dir),
    )
    logger.info("NER service initialized (model loaded on demand)")
    return ner_service


async def create_calendar_service(
    session_factory: async_sessionmaker[AsyncSession] | None,
    settings: Settings,
) -> CalendarService | None:
    """Create calendar service if needed.

    Args:
        session_factory: Database session factory.
        settings: Application settings.

    Returns:
        CalendarService if needed and database available, None otherwise.
    """
    calendar_needed = get_feature_flags().calendar_enabled or settings.trigger_calendar_enabled

    # Check database for existing OAuth connections
    if session_factory and not calendar_needed:
        async with SqlAlchemyUnitOfWork(session_factory, settings.meetings_dir) as uow:
            calendar_needed = await check_calendar_needed_from_db(uow)

    if not calendar_needed:
        return None

    if not session_factory:
        logger.warning(
            "Calendar feature enabled but no database configured. "
            "Calendar requires database for OAuth token persistence."
        )
        return None

    logger.info("Initializing calendar service...")
    calendar_settings = get_calendar_settings()
    calendar_service = CalendarService(
        uow_factory=lambda: SqlAlchemyUnitOfWork(session_factory, settings.meetings_dir),
        settings=calendar_settings,
    )
    logger.info("Calendar service initialized")
    return calendar_service


def create_diarization_engine(diarization: _DiarizationConfigLike) -> DiarizationEngine | None:
    """Create diarization engine if enabled and configured.

    Args:
        diarization: Diarization configuration.

    Returns:
        DiarizationEngine if enabled, None otherwise.
    """
    if not diarization.enabled:
        return None

    if not diarization.hf_token:
        logger.warning(
            "Diarization enabled but no HuggingFace token provided. "
            "Set NOTEFLOW_DIARIZATION_HF_TOKEN or --diarization-hf-token."
        )
        return None

    logger.info("Initializing diarization engine on %s...", diarization.device)
    diarization_kwargs: DiarizationEngineKwargs = {
        "device": diarization.device,
        "hf_token": diarization.hf_token,
    }
    if diarization.streaming_latency is not None:
        diarization_kwargs["streaming_latency"] = diarization.streaming_latency
    if diarization.min_speakers is not None:
        diarization_kwargs["min_speakers"] = diarization.min_speakers
    if diarization.max_speakers is not None:
        diarization_kwargs["max_speakers"] = diarization.max_speakers

    engine = DiarizationEngine(**diarization_kwargs)
    logger.info("Diarization engine initialized (models loaded on demand)")
    return engine


async def create_webhook_service(
    session_factory: async_sessionmaker[AsyncSession],
    settings: Settings,
) -> WebhookService | None:
    """Create and configure webhook service if enabled.

    Args:
        session_factory: Database session factory.
        settings: Application settings.

    Returns:
        WebhookService if enabled, None otherwise.
    """
    if not get_feature_flags().webhooks_enabled:
        return None

    logger.info("Initializing webhook service...")
    webhook_executor = WebhookExecutor()
    webhook_service = WebhookService(executor=webhook_executor)

    async with SqlAlchemyUnitOfWork(session_factory, settings.meetings_dir) as uow:
        webhook_configs = await uow.webhooks.get_all_enabled()
        for config_item in webhook_configs:
            webhook_service.register_webhook(config_item)
        logger.info(
            "Webhook service initialized with %d active webhooks",
            len(webhook_configs),
        )
    return webhook_service


def print_startup_banner(
    config: _GrpcServerConfigLike,
    diarization_engine: DiarizationEngine | None,
    cloud_llm_provider: str | None,
    calendar_service: CalendarService | None,
    webhook_service: WebhookService | None,
) -> None:
    """Print server startup status banner.

    Args:
        config: Server configuration.
        diarization_engine: Optional diarization engine.
        cloud_llm_provider: Cloud LLM provider name if enabled.
        calendar_service: Optional calendar service.
        webhook_service: Optional webhook service.
    """
    # Log for non-interactive contexts
    logger.info("NoteFlow server starting on port %d", config.port)
    logger.info("ASR model: %s (%s/%s)", config.asr.model, config.asr.device, config.asr.compute_type)
    logger.info("Database: %s", "Connected" if config.database_url else "In-memory mode")
    logger.info("Diarization: %s", f"Enabled ({config.diarization.device})" if diarization_engine else STATUS_DISABLED)
    logger.info("Summarization: %s", f"Cloud ({cloud_llm_provider})" if cloud_llm_provider else "Local only")
    logger.info("Calendar: %s", "Enabled" if calendar_service else STATUS_DISABLED)
    logger.info("Webhooks: %s", f"Enabled ({len(webhook_service.configs)} registered)" if webhook_service else STATUS_DISABLED)

    # Rich console for interactive terminals
    if sys.stdout.isatty():
        console = Console()
        console.print(f"\n[bold green]NoteFlow server running on port {config.port}[/bold green]")
        console.print(
            f"ASR model: {config.asr.model} "
            f"({config.asr.device}/{config.asr.compute_type})"
        )
        db_status = "[green]Connected[/green]" if config.database_url else "[yellow]In-memory mode[/yellow]"
        console.print(f"Database: {db_status}")
        diar_status = f"[green]Enabled[/green] ({config.diarization.device})" if diarization_engine else "[dim]Disabled[/dim]"
        console.print(f"Diarization: {diar_status}")
        summ_status = f"[green]Cloud enabled[/green] ({cloud_llm_provider})" if cloud_llm_provider else "[dim]Local only (Ollama/Mock)[/dim]"
        console.print(f"Summarization: {summ_status}")
        console.print(f"Calendar: {'[green]Enabled[/green]' if calendar_service else '[dim]Disabled[/dim]'}")
        if webhook_service:
            console.print(f"Webhooks: [green]Enabled[/green] ({len(webhook_service.configs)} registered)")
        else:
            console.print("Webhooks: [dim]Disabled[/dim]")
        console.print("[dim]Press Ctrl+C to stop[/dim]\n")
