from fastapi import Depends, Query
from typing import AsyncGenerator
from src.domain.entities.user import User
from fastapi.security import OAuth2PasswordBearer
from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession
from sqlalchemy.orm import sessionmaker
from src.application.services.authentication_service import AuthService
from src.application.ports.authentication_service_port import AuthServicePort
from src.application.ports.file_storage_service import FileStoragePort
from src.application.use_cases.upload_chart import UploadChartUseCase
from src.domain.ports.repositories.user_repository import UserRepositoryPort
from src.domain.ports.repositories.token_repository import TokenRepositoryPort
from src.domain.ports.repositories.charts_repository import ChartsRepositoryPort
from src.domain.ports.repositories.conversation_repository import ConversationRepositoryPort
from src.infrastructure.persistence.repositories.sql_user_repository import SqlUserRepository
from src.infrastructure.persistence.repositories.sql_charts_repository import SqlChartsRepository
from src.infrastructure.persistence.repositories.sql_conversation_repository import SqlConversationRepository
from src.application.services.analyze_service import AnalyzeService
from src.infrastructure.services.ollama_service import OllamaService
from src.infrastructure.services.chartGemma_service import ChartGemmaService
from src.application.services.conversation_service import ConversationService
from src.application.ports.llm_service_port import LLMServicePort
from src.infrastructure.persistence.repositories.sql_token_repository import SqlTokenRepository
from src.config import settings

engine = create_async_engine(settings.DATABASE_URL, echo=True)
AsyncSessionLocal = sessionmaker(engine, class_=AsyncSession, expire_on_commit=False)

async def get_db_session() -> AsyncGenerator[AsyncSession, None]:
    async with AsyncSessionLocal() as session:
        yield session

def get_user_repository(session: AsyncSession = Depends(get_db_session)) -> UserRepositoryPort:
    return SqlUserRepository(session)

def get_charts_repository(session: AsyncSession = Depends(get_db_session)) -> ChartsRepositoryPort:
    return SqlChartsRepository(session)

def get_conversation_repository(session: AsyncSession = Depends(get_db_session)) -> ConversationRepositoryPort:
    return SqlConversationRepository(session)

def get_token_repo(session: AsyncSession = Depends(get_db_session)) -> TokenRepositoryPort:
    return SqlTokenRepository(session)

def get_auth_service(
    user_repo: UserRepositoryPort = Depends(get_user_repository),
    token_repo: TokenRepositoryPort = Depends(get_token_repo)
) -> AuthService:
    return AuthService(user_repo, token_repo)

def get_upload_use_case(charts_repo: ChartsRepositoryPort = Depends(get_charts_repository)) -> UploadChartUseCase:
    return UploadChartUseCase(charts_repo)

def get_analysis_service() -> AnalyzeService:
    """Factory for the analysis service"""
    llm_service = get_llm_service()
    return AnalyzeService(llm_service)

def get_ollama_service() -> OllamaService:
    """Factory for Ollama LLM service"""
    return OllamaService(host=settings.OLLAMA_HOST)

def get_chartgemma_service() -> ChartGemmaService:
    """Factory for ChartGemma LLM service"""
    return ChartGemmaService(gradio_url=settings.CHARTGEMMA_GRADIO_URL)

def get_llm_service(model: str = settings.DEFAULT_LLM_MODEL) -> LLMServicePort:
    """
    Factory function to get the appropriate LLM service based on model selection.
    This allows easy switching between different LLM providers.
    """
    if model.lower() == "ollama":
        return get_ollama_service()
    elif model.lower() == "chartgemma":
        return get_chartgemma_service()
    else:
        # Default to Ollama if unknown model is specified
        return get_ollama_service()

def get_llm_service_by_query(
    model: str = Query(default=settings.DEFAULT_LLM_MODEL, description="LLM model to use (ollama or chartgemma)")
) -> LLMServicePort:
    """
    Dependency function that allows model selection via query parameter.
    This provides a clean way for users to select their preferred LLM model.
    """
    return get_llm_service(model)

oauth2_scheme = OAuth2PasswordBearer(tokenUrl="auth/token")

async def get_current_user(
    token: str = Depends(oauth2_scheme),
    auth_service: AuthServicePort = Depends(get_auth_service)
) -> User:
    """Dependency to get current authenticated user"""
    try:
        user = await auth_service.get_current_user(token)
        if not user:
            raise HTTPException(
                status_code=status.HTTP_401_UNAUTHORIZED,
                detail="Invalid authentication credentials",
                headers={"WWW-Authenticate": "Bearer"},
            )
        return user
    except JWTError:
        raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail="Could not validate credentials",
            headers={"WWW-Authenticate": "Bearer"},
        )

def get_file_storage_service() -> FileStoragePort:
    """Factory for file storage service (switches between local and cloud)"""
    if settings.USE_CLOUD_STORAGE:
        return CloudFileStorageService(
            bucket_name=settings.STORAGE_BUCKET,
            credentials_path=settings.GOOGLE_APPLICATION_CREDENTIALS
        )
    return LocalFileStorageService(storage_root=settings.STORAGE_ROOT)

def get_conversation_service(
    conversation_repo: ConversationRepositoryPort = Depends(get_conversation_repository),
    charts_repo: ChartsRepositoryPort = Depends(get_charts_repository),
    file_storage: FileStoragePort = Depends(get_file_storage_service),
    llm_service: LLMServicePort = Depends(get_llm_service)
) -> ConversationService:
    """Factory for conversation service with all dependencies"""
    return ConversationService(
        conversation_repo=conversation_repo,
        chart_repo=charts_repo,
        file_storage=file_storage,
        llm_service=llm_service
    )

