from sqlalchemy.orm import Session
from fastapi import HTTPException, status
from typing import List, Optional
from datetime import datetime
from . import repository
from .schema import (
    ConversationCreate,
    ConversationUpdate,
    ConversationResponse,
    ConversationDetailResponse,
    MessageCreate,
    MessageUpdate,
    MessageResponse
)
from user_management.models import User
from listing_management.models import Listing
from order_management.models import Order


def _get_or_raise(db: Session, entity_id: str, repo_module) -> any:
    entity = repo_module.get_by_id(db, entity_id)
    if not entity:
        raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=f"Entity with id {entity_id} not found")
    return entity


def create_conversation(db: Session, data: ConversationCreate) -> ConversationResponse:
    participant1 = db.query(User).filter(User.id == data.participant1_id).first()
    if not participant1:
        raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Participant 1 not found")
    
    participant2 = db.query(User).filter(User.id == data.participant2_id).first()
    if not participant2:
        raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Participant 2 not found")
    
    if data.participant1_id == data.participant2_id:
        raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Participants must be different users")
    
    if data.listing_id:
        listing = db.query(Listing).filter(Listing.id == data.listing_id).first()
        if not listing:
            raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Listing not found")
    
    if data.order_id:
        order = db.query(Order).filter(Order.id == data.order_id).first()
        if not order:
            raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Order not found")
    
    try:
        conversation = repository.create(db, data.model_dump())
        db.commit()
        db.refresh(conversation)
        return ConversationResponse.model_validate(conversation)
    except Exception:
        db.rollback()
        raise


def get_conversation(db: Session, conversation_id: str) -> ConversationResponse:
    conversation = _get_or_raise(db, conversation_id, repository)
    return ConversationResponse.model_validate(conversation)


def list_conversations(
    db: Session,
    limit: int = 20,
    offset: int = 0,
    participant1_id: Optional[str] = None,
    participant2_id: Optional[str] = None,
    listing_id: Optional[str] = None,
    order_id: Optional[str] = None
) -> List[ConversationResponse]:
    filters = {}
    if participant1_id:
        filters["participant1_id"] = participant1_id
    if participant2_id:
        filters["participant2_id"] = participant2_id
    if listing_id:
        filters["listing_id"] = listing_id
    if order_id:
        filters["order_id"] = order_id
    
    conversations = repository.list_all(db, limit, offset, **filters)
    return [ConversationResponse.model_validate(c) for c in conversations]


def update_conversation(db: Session, conversation_id: str, data: ConversationUpdate) -> ConversationResponse:
    _get_or_raise(db, conversation_id, repository)
    
    update_data = data.model_dump(exclude_unset=True)
    
    if "participant1_id" in update_data:
        participant1 = db.query(User).filter(User.id == update_data["participant1_id"]).first()
        if not participant1:
            raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Participant 1 not found")
    
    if "participant2_id" in update_data:
        participant2 = db.query(User).filter(User.id == update_data["participant2_id"]).first()
        if not participant2:
            raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Participant 2 not found")
    
    if "listing_id" in update_data and update_data["listing_id"]:
        listing = db.query(Listing).filter(Listing.id == update_data["listing_id"]).first()
        if not listing:
            raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Listing not found")
    
    if "order_id" in update_data and update_data["order_id"]:
        order = db.query(Order).filter(Order.id == update_data["order_id"]).first()
        if not order:
            raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Order not found")
    
    try:
        conversation = repository.update(db, conversation_id, update_data)
        db.commit()
        db.refresh(conversation)
        return ConversationResponse.model_validate(conversation)
    except Exception:
        db.rollback()
        raise


def delete_conversation(db: Session, conversation_id: str) -> dict:
    _get_or_raise(db, conversation_id, repository)
    try:
        repository.delete(db, conversation_id)
        db.commit()
        return {"message": "Conversation deleted successfully"}
    except Exception:
        db.rollback()
        raise


def get_conversation_details(db: Session, conversation_id: str) -> ConversationDetailResponse:
    conversation = repository.get_with_details(db, conversation_id)
    if not conversation:
        raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Conversation not found")
    return ConversationDetailResponse.model_validate(conversation)


def create_message(db: Session, data: MessageCreate) -> MessageResponse:
    conversation = db.query(repository.Conversation).filter(repository.Conversation.id == data.conversation_id).first()
    if not conversation:
        raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Conversation not found")
    
    sender = db.query(User).filter(User.id == data.sender_id).first()
    if not sender:
        raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Sender not found")
    
    if data.sender_id not in [conversation.participant1_id, conversation.participant2_id]:
        raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Sender must be a participant in the conversation")
    
    try:
        message = repository.create_message(db, data.model_dump())
        conversation.last_message_at = datetime.utcnow()
        db.flush()
        db.commit()
        db.refresh(message)
        return MessageResponse.model_validate(message)
    except Exception:
        db.rollback()
        raise


def get_message(db: Session, message_id: str) -> MessageResponse:
    message = repository.get_message_by_id(db, message_id)
    if not message:
        raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Message not found")
    return MessageResponse.model_validate(message)


def list_messages(
    db: Session,
    limit: int = 20,
    offset: int = 0,
    conversation_id: Optional[str] = None,
    sender_id: Optional[str] = None,
    is_read: Optional[bool] = None
) -> List[MessageResponse]:
    filters = {}
    if conversation_id:
        filters["conversation_id"] = conversation_id
    if sender_id:
        filters["sender_id"] = sender_id
    if is_read is not None:
        filters["is_read"] = is_read
    
    messages = repository.list_messages(db, limit, offset, **filters)
    return [MessageResponse.model_validate(m) for m in messages]


def update_message(db: Session, message_id: str, data: MessageUpdate) -> MessageResponse:
    message = repository.get_message_by_id(db, message_id)
    if not message:
        raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Message not found")
    
    update_data = data.model_dump(exclude_unset=True)
    
    if "conversation_id" in update_data:
        conversation = db.query(repository.Conversation).filter(repository.Conversation.id == update_data["conversation_id"]).first()
        if not conversation:
            raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Conversation not found")
    
    if "sender_id" in update_data:
        sender = db.query(User).filter(User.id == update_data["sender_id"]).first()
        if not sender:
            raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Sender not found")
    
    try:
        message = repository.update_message(db, message_id, update_data)
        db.commit()
        db.refresh(message)
        return MessageResponse.model_validate(message)
    except Exception:
        db.rollback()
        raise


def delete_message(db: Session, message_id: str) -> dict:
    message = repository.get_message_by_id(db, message_id)
    if not message:
        raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Message not found")
    try:
        repository.delete_message(db, message_id)
        db.commit()
        return {"message": "Message deleted successfully"}
    except Exception:
        db.rollback()
        raise


def mark_message_as_read(db: Session, message_id: str) -> MessageResponse:
    message = repository.get_message_by_id(db, message_id)
    if not message:
        raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Message not found")
    
    if message.is_read:
        return MessageResponse.model_validate(message)
    
    try:
        update_data = {"is_read": True, "read_at": datetime.utcnow()}
        message = repository.update_message(db, message_id, update_data)
        db.commit()
        db.refresh(message)
        return MessageResponse.model_validate(message)
    except Exception:
        db.rollback()
        raise


def get_user_conversations(db: Session, user_id: str, limit: int = 20, offset: int = 0) -> List[ConversationResponse]:
    user = db.query(User).filter(User.id == user_id).first()
    if not user:
        raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="User not found")
    
    conversations = repository.get_conversations_for_user(db, user_id, limit, offset)
    return [ConversationResponse.model_validate(c) for c in conversations]