from sqlalchemy.orm import Session
from fastapi import HTTPException, status
from typing import List, Optional
from datetime import datetime, timedelta
from . import repository
from .schema import OfferCreate, OfferUpdate, OfferResponse
from user_management import repository as user_repo
from listing_management import repository as listing_repo

def _get_or_raise(db: Session, entity_id: str, repo) -> any:
    entity = repo.get_by_id(db, entity_id)
    if not entity:
        raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Resource not found")
    return entity

def create_offer(db: Session, data: OfferCreate) -> OfferResponse:
    buyer = user_repo.get_by_id(db, data.buyer_id)
    if not buyer:
        raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Buyer not found")
    
    listing = listing_repo.get_by_id(db, data.listing_id)
    if not listing:
        raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Listing not found")
    
    if not listing.accepts_offers:
        raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Listing does not accept offers")
    
    if listing.status != "Active":
        raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Listing is not active")
    
    if data.offer_amount < (listing.price * 0.5):
        raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Offer must be at least 50% of asking price")
    
    if data.offer_amount > listing.price:
        raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Offer amount cannot exceed asking price")
    
    offer_data = data.model_dump()
    offer_data["status"] = "Pending"
    
    try:
        offer = repository.create(db, offer_data)
        db.commit()
        db.refresh(offer)
        return OfferResponse.model_validate(offer)
    except Exception:
        db.rollback()
        raise

def get_offer(db: Session, offer_id: str) -> OfferResponse:
    offer = _get_or_raise(db, offer_id, repository)
    return OfferResponse.model_validate(offer)

def list_offers(
    db: Session,
    limit: int = 20,
    offset: int = 0,
    listing_id: Optional[str] = None,
    buyer_id: Optional[str] = None,
    status: Optional[str] = None
) -> List[OfferResponse]:
    filters = {}
    if listing_id:
        filters["listing_id"] = listing_id
    if buyer_id:
        filters["buyer_id"] = buyer_id
    if status:
        filters["status"] = status
    
    offers = repository.list_all(db, limit, offset, **filters)
    return [OfferResponse.model_validate(offer) for offer in offers]

def update_offer(db: Session, offer_id: str, data: OfferUpdate) -> OfferResponse:
    offer = _get_or_raise(db, offer_id, repository)
    
    update_data = data.model_dump(exclude_unset=True)
    
    if "buyer_id" in update_data:
        buyer = user_repo.get_by_id(db, update_data["buyer_id"])
        if not buyer:
            raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Buyer not found")
    
    if "listing_id" in update_data:
        listing = listing_repo.get_by_id(db, update_data["listing_id"])
        if not listing:
            raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Listing not found")
    
    try:
        updated_offer = repository.update(db, offer_id, update_data)
        db.commit()
        db.refresh(updated_offer)
        return OfferResponse.model_validate(updated_offer)
    except Exception:
        db.rollback()
        raise

def delete_offer(db: Session, offer_id: str) -> dict:
    offer = _get_or_raise(db, offer_id, repository)
    
    try:
        repository.delete(db, offer_id)
        db.commit()
        return {"message": "Offer deleted successfully"}
    except Exception:
        db.rollback()
        raise

def accept_offer(db: Session, offer_id: str) -> OfferResponse:
    offer = _get_or_raise(db, offer_id, repository)
    
    if offer.status != "Pending":
        raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Only pending offers can be accepted")
    
    if offer.expires_at < datetime.utcnow():
        raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Offer has expired")
    
    listing = listing_repo.get_by_id(db, offer.listing_id)
    if not listing:
        raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Listing not found")
    
    if listing.status != "Active":
        raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Listing is not active")
    
    try:
        update_data = {
            "status": "Accepted",
            "responded_at": datetime.utcnow()
        }
        updated_offer = repository.update(db, offer_id, update_data)
        
        listing_update = {"status": "Reserved"}
        listing_repo.update(db, listing.id, listing_update)
        
        db.commit()
        db.refresh(updated_offer)
        return OfferResponse.model_validate(updated_offer)
    except Exception:
        db.rollback()
        raise

def reject_offer(db: Session, offer_id: str, response_message: Optional[str] = None) -> OfferResponse:
    offer = _get_or_raise(db, offer_id, repository)
    
    if offer.status != "Pending":
        raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Only pending offers can be rejected")
    
    try:
        update_data = {
            "status": "Rejected",
            "responded_at": datetime.utcnow(),
            "response_message": response_message
        }
        updated_offer = repository.update(db, offer_id, update_data)
        db.commit()
        db.refresh(updated_offer)
        return OfferResponse.model_validate(updated_offer)
    except Exception:
        db.rollback()
        raise

def counter_offer(db: Session, offer_id: str, counter_amount: float, response_message: Optional[str] = None) -> OfferResponse:
    offer = _get_or_raise(db, offer_id, repository)
    
    if offer.status != "Pending":
        raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Only pending offers can be countered")
    
    listing = listing_repo.get_by_id(db, offer.listing_id)
    if not listing:
        raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Listing not found")
    
    if counter_amount <= 0:
        raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Counter amount must be positive")
    
    if counter_amount > listing.price:
        raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Counter amount cannot exceed asking price")
    
    try:
        reject_data = {
            "status": "Rejected",
            "responded_at": datetime.utcnow(),
            "response_message": response_message or f"Counter offer: ${counter_amount}"
        }
        repository.update(db, offer_id, reject_data)
        
        new_offer_data = {
            "listing_id": offer.listing_id,
            "buyer_id": offer.buyer_id,
            "offer_amount": counter_amount,
            "message": response_message,
            "status": "Pending",
            "expires_at": datetime.utcnow() + timedelta(hours=48)
        }
        new_offer = repository.create(db, new_offer_data)
        
        db.commit()
        db.refresh(new_offer)
        return OfferResponse.model_validate(new_offer)
    except Exception:
        db.rollback()
        raise

def cancel_offer(db: Session, offer_id: str) -> OfferResponse:
    offer = _get_or_raise(db, offer_id, repository)
    
    if offer.status != "Pending":
        raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Only pending offers can be cancelled")
    
    try:
        update_data = {"status": "Cancelled"}
        updated_offer = repository.update(db, offer_id, update_data)
        db.commit()
        db.refresh(updated_offer)
        return OfferResponse.model_validate(updated_offer)
    except Exception:
        db.rollback()
        raise