from collections.abc import Callable
from typing import Awaitable, Optional

from fastapi import APIRouter

from spaik_sdk.agent.base_agent import BaseAgent
from spaik_sdk.attachments.file_storage_provider import set_file_storage
from spaik_sdk.attachments.storage.base_file_storage import BaseFileStorage
from spaik_sdk.attachments.storage.impl.local_file_storage import LocalFileStorage
from spaik_sdk.server.api.routers.audio_router_factory import AudioRouterFactory
from spaik_sdk.server.api.routers.file_router_factory import FileRouterFactory
from spaik_sdk.server.api.routers.thread_router_factory import ThreadRouterFactory
from spaik_sdk.server.api.streaming.streaming_negotiator import StreamingNegotiator
from spaik_sdk.server.authorization.base_authorizer import BaseAuthorizer
from spaik_sdk.server.authorization.base_user import BaseUser
from spaik_sdk.server.authorization.dummy_authorizer import DummyAuthorizer
from spaik_sdk.server.job_processor.thread_job_processor import ThreadJobProcessor
from spaik_sdk.server.pubsub.cancellation_publisher import CancellationPublisher
from spaik_sdk.server.pubsub.cancellation_subscriber import CancellationSubscriber
from spaik_sdk.server.pubsub.impl.local_cancellation_pubsub import get_local_cancellation_pubsub
from spaik_sdk.server.queue.agent_job_queue import AgentJobQueue
from spaik_sdk.server.response.response_generator import ResponseGenerator
from spaik_sdk.server.response.simple_agent_response_generator import SimpleAgentResponseGenerator
from spaik_sdk.server.services.thread_service import ThreadService
from spaik_sdk.server.storage.base_thread_repository import BaseThreadRepository
from spaik_sdk.server.storage.impl.in_memory_thread_repository import InMemoryThreadRepository
from spaik_sdk.server.storage.impl.local_file_thread_repository import LocalFileThreadRepository


class ApiBuilder:
    def __init__(
        self,
        repository: BaseThreadRepository,
        authorizer: Optional[BaseAuthorizer[BaseUser]] = None,
        streaming_negotiator: Optional[StreamingNegotiator] = None,
        job_queue: Optional[AgentJobQueue] = None,
        cancellation_subscriber_provider: Optional[Callable[[str], Awaitable[CancellationSubscriber]]] = None,
        cancellation_publisher: Optional[CancellationPublisher] = None,
        response_generator: Optional[ResponseGenerator] = None,
        agent: Optional[BaseAgent] = None,
        file_storage: Optional[BaseFileStorage] = None,
    ):
        self.repository = repository
        self.thread_service = ThreadService(repository)
        self.authorizer = authorizer
        self.streaming_negotiator = streaming_negotiator
        self.job_queue = job_queue
        self.cancellation_subscriber_provider = cancellation_subscriber_provider
        self.cancellation_publisher = cancellation_publisher
        self.file_storage = file_storage
        if not response_generator and agent:
            self.response_generator: Optional[ResponseGenerator] = SimpleAgentResponseGenerator(agent)
        else:
            self.response_generator = response_generator

    def build_file_router(self) -> APIRouter:
        if not self.file_storage:
            raise ValueError("File storage is required for file router")
        factory = FileRouterFactory(
            file_storage=self.file_storage,
            authorizer=self.authorizer,
        )
        return factory.create_router()

    def build_audio_router(
        self,
        tts_model: Optional[str] = None,
        stt_model: Optional[str] = None,
    ) -> APIRouter:
        """
        Build the audio router for TTS/STT endpoints.

        Args:
            tts_model: Default TTS model (e.g., "tts-1", "gemini-2.5-flash-tts")
            stt_model: Default STT model (e.g., "whisper-1")

        Returns:
            FastAPI router with /audio/speech and /audio/transcribe endpoints
        """
        factory = AudioRouterFactory(
            authorizer=self.authorizer,
            tts_model=tts_model,
            stt_model=stt_model,
        )
        return factory.create_router()

    def build_thread_router(self) -> APIRouter:
        if not self.response_generator:
            raise ValueError("Response generator or agent is required")

        job_processor = ThreadJobProcessor(thread_service=self.thread_service, response_generator=self.response_generator)
        factory = ThreadRouterFactory(
            service=self.thread_service,
            authorizer=self.authorizer,
            streaming_negotiator=self.streaming_negotiator,
            job_queue=self.job_queue,
            thread_job_processor=job_processor,
            cancellation_subscriber_provider=self.cancellation_subscriber_provider,
            cancellation_publisher=self.cancellation_publisher,
        )
        return factory.create_router()

    @classmethod
    def stateful(
        cls,
        repository: BaseThreadRepository,
        authorizer: BaseAuthorizer[BaseUser],
        agent: Optional[BaseAgent] = None,
        response_generator: Optional[ResponseGenerator] = None,
        file_storage: Optional[BaseFileStorage] = None,
    ) -> "ApiBuilder":
        cancellation_pubsub = get_local_cancellation_pubsub()

        async def cancellation_subscriber_provider(id: str) -> CancellationSubscriber:
            return cancellation_pubsub.create_subscriber(id)

        # Set the singleton if file_storage is provided
        if file_storage is not None:
            set_file_storage(file_storage)

        return ApiBuilder(
            repository=repository,
            authorizer=authorizer,
            cancellation_subscriber_provider=cancellation_subscriber_provider,
            cancellation_publisher=cancellation_pubsub.get_publisher(),
            agent=agent,
            response_generator=response_generator,
            file_storage=file_storage,
        )

    @classmethod
    def local(
        cls,
        agent: Optional[BaseAgent] = None,
        response_generator: Optional[ResponseGenerator] = None,
        in_memory: bool = False,
        file_storage: Optional[BaseFileStorage] = None,
    ) -> "ApiBuilder":
        # Use provided file_storage or create a local one
        storage = file_storage or LocalFileStorage()
        # Also set the singleton so LangChainService can access it
        set_file_storage(storage)
        return cls.stateful(
            repository=InMemoryThreadRepository() if in_memory else LocalFileThreadRepository(),
            authorizer=DummyAuthorizer(),
            agent=agent,
            response_generator=response_generator,
            file_storage=storage,
        )
