import uuid
import time
import stripe
from datetime import datetime, timezone
from pathlib import Path
import json
from core.config import settings
from core.security.exceptions import GenericError
from models import Event,EventParticipant,EventCart,EventPayment,DiscountCoupon,Parent,Participant, WalletHistory
from schema.event import EventResponseSchema
from core.config import get_db, settings,BASE_PATH
from sqlalchemy.orm import joinedload
from core.utility import send_event_order_summary_email
import os

class EventService:
    currency = "gbp"

    async def upload_image(self, request):
        if request.image:
            file_extension = Path(request.image.filename).suffix
            new_filename = f"{uuid.uuid4()}{file_extension}"
            file_location = BASE_PATH / f'uploads/{new_filename}'
            content = await request.image.read()
            with open(file_location, "wb") as f:
                f.write(content)
            return new_filename
        return None

    async def create_event(self, request):
        # Handle event image
        event_image = await self.upload_image(request)
        
        # Handle participant images
        participant_images = []
        for image in request.participant_images:
            if image:
                file_extension = Path(image.filename).suffix
                new_filename = f"{uuid.uuid4()}{file_extension}"
                file_location = BASE_PATH / f'uploads/{new_filename}'
                content = await image.read()
                with open(file_location, "wb") as f:
                    f.write(content)
                participant_images.append(new_filename)
            else:
                participant_images.append(None)
        
        with get_db() as db:
            try:
                db.begin()
                
                event = Event(
                    name=request.name,
                    event_start_date=request.event_start_date,
                    event_end_date=request.event_end_date,
                    event_start_time=request.event_start_time,
                    event_end_time=request.event_end_time,
                    price=request.price,
                    description=request.description,
                    location=request.location,
                    image=event_image
                )
                db.add(event)
                db.commit()
                db.refresh(event)
                
                # Create participants with names and images (handling None images)
                for name, image in zip(request.participant_names, participant_images):
                    db_participant = EventParticipant(
                        event_id=event.id,
                        name=name,
                        image=image
                    )
                    db.add(db_participant)
                
                db.commit()
                return event
                
            except Exception as e:
                db.rollback()
                raise GenericError(status_code=500, exc=f"Error creating event: {str(e)}")

    def fetch_all_event(self):
        with get_db() as db:
            records = db.query(Event).where(Event.deleted_at.is_(None)).all()
            events = []
            for record in records:
                event_data = EventResponseSchema.model_validate(record).model_dump()
                participants = db.query(EventParticipant).where(
                    EventParticipant.event_id == record.id
                ).all()
                event_data['participants'] = [
                    {"name": p.name, "image": p.image,"image_url":p.image_url} for p in participants
                ]
                events.append(event_data)
            return events

    def fetch_event_details(self, event_id: int):
        with get_db() as db:
            record = db.query(Event).where(Event.id == event_id,
                                           Event.deleted_at.is_(None)).first()
    
            if not record:
                raise GenericError(status_code=404, exc="Event not found")
                
            participants = db.query(EventParticipant).where(
                EventParticipant.event_id == event_id
            ).all()
                
            event_data = EventResponseSchema.model_validate(record).model_dump()
            event_data['participants'] = [
                {"name": p.name, "image": p.image,"image_url":p.image_url} for p in participants
            ]
            return event_data

    async def update_event(self, event_id, request):
        payload = request.model_dump(exclude=['id', 'image', 'participant_images', 'participant_names'])
        
        # Handle event image update
        # Only update image if a new one was provided
        if hasattr(request, 'image') and request.image is not None:
            image = await self.upload_image(request)
            payload['image'] = image
        
        # Handle participant images update
        participant_images = []
        if hasattr(request, 'participant_images') and request.participant_images is not None:
            for image in request.participant_images:
                if image:
                    file_extension = Path(image.filename).suffix
                    new_filename = f"{uuid.uuid4()}{file_extension}"
                    file_location = BASE_PATH / f'uploads/{new_filename}'
                    content = await image.read()
                    with open(file_location, "wb") as f:
                        f.write(content)
                    participant_images.append(new_filename)
                else:
                    participant_images.append(None)
        
        with get_db() as db:
            # Update event details
            result = db.query(Event).where(Event.id == event_id, Event.deleted_at.is_(None)).update(payload,
                                                                                                 synchronize_session="fetch")
            
            # Add new participants if provided, without removing existing ones
            if hasattr(request, 'participant_names') and request.participant_names is not None:
                # Add new participants
                # If participant_images are not provided, create a list of None values
                if not hasattr(request, 'participant_images') or request.participant_images is None:
                    participant_images = [None] * len(request.participant_names)
                
                # Ensure both lists have the same length
                names = request.participant_names
                images = participant_images
                
                if len(names) != len(images):
                    raise GenericError(status_code=422, exc="Participant names and images count mismatch")
                
                for name, image in zip(names, images):
                    # Check if participant already exists
                    existing_participant = db.query(EventParticipant).filter(
                        EventParticipant.event_id == event_id,
                        EventParticipant.name == name
                    ).first()
                    
                    # Only add if participant doesn't already exist
                    if not existing_participant:
                        db_participant = EventParticipant(
                            event_id=event_id,
                            name=name,
                            image=image
                        )
                        db.add(db_participant)
            
            db.commit()
            if result == 0:
                raise GenericError(status_code=422, exc="Could not update event")

    def delete_event(self, event_id):
        with get_db() as db:
            event = db.query(Event).where(Event.id == event_id, Event.deleted_at.is_(None)).first()
            if not event:
                raise GenericError(status_code=404, exc="Event not found")

            event.deleted_at = datetime.now(timezone.utc)
            db.commit()
            return {"message": "Event deleted successfully"}

    def event_filter(self, request):
        with get_db() as db:

            query = db.query(Event).filter(Event.deleted_at.is_(None))
            if request.event_start_date:
                query = query.filter(Event.event_start_date == request.event_start_date)
            if request.event_end_date:
                query = query.filter(Event.event_end_date == request.event_end_date)
            records = query.all()
            return [EventResponseSchema.model_validate(record).model_dump() for record in records]

    """
    Create an event cart.
    """

    async def create_event_cart(self, request,user):
        with get_db() as db:
            try:
                db.begin()
                # Fetch the event to get its price
                event = db.query(Event).filter(Event.id == request.event_id).first()
                if not event:
                    raise GenericError(status_code=404, exc="Event not found")
                
                # Calculate total amount
                total_amount = event.price * request.no_of_participants
                
                event_cart = EventCart(
                    event_id=request.event_id,
                    user_id=user['id'],
                    user_type=user['user_type'],
                    no_of_participants=request.no_of_participants,
                    total_amount=total_amount
                )
                db.add(event_cart)
                db.commit()
                db.refresh(event_cart)
                return event_cart
            except Exception as e:
                db.rollback()
                raise GenericError(status_code=500, exc=f"Error creating event cart: {str(e)}")

    def _generate_event_cart_response(self, cart, event):
        """Generate response dictionary for event cart with event details"""
        return {
            'id': cart.id,
            'event_id': cart.event_id,
            'user_id': cart.user_id,
            'user_type': cart.user_type,
            'no_of_participants': cart.no_of_participants,
            'total_amount': cart.total_amount,
            'created_at': cart.created_at,
            'updated_at': cart.updated_at,
            'event': {
                'id': event.id,
                'name': event.name,
                'event_start_date': event.event_start_date,
                'event_end_date': event.event_end_date,
                'event_start_time': event.event_start_time,
                'event_end_time': event.event_end_time,
                'price': event.price,
                'description': event.description,
                'location': event.location,
                'image': event.image
            }
        }

    def get_event_cart_by_user_id(self,user):
        with get_db() as db:
            data = db.query(EventCart, Event).join(
                Event, EventCart.event_id == Event.id
            ).where(
                EventCart.user_id == user['id'], 
                EventCart.user_type == user['user_type'],
                EventCart.deleted_at.is_(None),
                Event.deleted_at.is_(None),
                EventCart.is_cart_processed==False,
            ).all()
            
            return [self._generate_event_cart_response(cart, event) for cart, event in data]

    def fetch_event_cart_details(self, event_cart_id: int):
        with get_db() as db:
            # Query both cart and event using join
            result = db.query(EventCart, Event).join(
                Event, EventCart.event_id == Event.id
            ).where(
                EventCart.id == event_cart_id,
                EventCart.deleted_at.is_(None),
                Event.deleted_at.is_(None)
            ).first()
            
            if not result:
                raise GenericError(status_code=404, exc="Event cart not found")
                
            cart, event = result
            return self._generate_event_cart_response(cart, event)

    async def update_event_cart(self, event_cart_id: int, request):
        with get_db() as db:
            result = db.query(EventCart).where(
                EventCart.id == event_cart_id,
                EventCart.deleted_at.is_(None)
            ).update(request.model_dump(exclude_unset=True))
            db.commit()
            if result == 0:
                raise GenericError(status_code=422, exc="Could not update event cart")

    def delete_event_cart(self, event_cart_id: int):
        with get_db() as db:
            cart = db.query(EventCart).where(
                EventCart.id == event_cart_id,
                EventCart.deleted_at.is_(None)
            ).first()
            if not cart:
                raise GenericError(status_code=404, exc="Event cart not found")
            cart.deleted_at = datetime.now(timezone.utc)
            db.commit()
            return {"message": "Event cart deleted successfully"}

    def apply_discount(self, cart_total_amount, discount_coupon_instance, user_id):
        if discount_coupon_instance:
            # First check if customer has exceeded usage limit
            with get_db() as db:
                usage_count = db.query(EventPayment).filter(
                    EventPayment.discount_coupon_id == discount_coupon_instance.id,
                    EventPayment.user_id == user_id
                ).count()
                
                if usage_count >= discount_coupon_instance.uses_per_customer:
                    return float(cart_total_amount), 0
                    
            calculated_discount = 0
            if discount_coupon_instance.rule_type:
                if float(cart_total_amount) >= discount_coupon_instance.min_cart_value:
                    if discount_coupon_instance.discount_type == 'percentage':
                        calculated_discount = float(cart_total_amount) * float(discount_coupon_instance.discount_value) / 100
                    else:
                        calculated_discount = float(discount_coupon_instance.discount_value)
            else:
                if discount_coupon_instance.discount_type == 'percentage':
                    calculated_discount = float(cart_total_amount) * float(discount_coupon_instance.discount_value) / 100
                else:
                    calculated_discount = float(discount_coupon_instance.discount_value)
            
            if hasattr(discount_coupon_instance, 'max_discount') and discount_coupon_instance.max_discount:
                calculated_discount = min(calculated_discount, float(discount_coupon_instance.max_discount))
            
            # Cap discount to prevent negative total
            calculated_discount = min(calculated_discount, cart_total_amount)
            
            cart_total_amount = max(0, float(cart_total_amount) - calculated_discount)
        return float(cart_total_amount), calculated_discount

    def fetch_wallet_amount(self, request, user):
        wallet_amount = 0
        if request.use_wallet:
            with get_db() as db:
                if user['user_type'] == 'parent':
                    wallet_amount = db.query(Parent).get(user['id']).wallet_amount
                elif user['user_type'] == 'participant':
                    wallet_amount = db.query(Participant).filter(Participant.id == user['id'],
                                                                 Participant.parent_id.is_(None)).first().wallet_amount
        return wallet_amount

    async def create_event_payment_session(self, request, user):
        """
        Creates a payment session for event cart items.
        
        Args:
            request: Payment request containing discount_coupon_code and charity flag
            user: Dictionary containing user id and user_type
            
        Returns:
            Dictionary containing payment_url, status, transaction details
            
        Raises:
            GenericError: If cart is empty or payment creation fails
        """
        session_url = None
        payment_status = "pending"
        paid_on = None
        is_payment_completed = False
        wallet_amount = 0
        used_wallet_amount = 0
    
        with get_db() as db:
            try:
                # Verify user exists first
                if user['user_type'] == 'parent':
                    user_exists = db.query(Parent).filter(Parent.id == user['id']).first()
                elif user['user_type'] == 'participant':
                    user_exists = db.query(Participant).filter(Participant.id == user['id']).first()
                
                if not user_exists:
                    raise GenericError(status_code=404, exc="User not found")
                
                
                
                # Fetch event cart items with their associated event data
                event_carts = db.query(EventCart).options(
                    joinedload(EventCart.event)
                ).filter(
                    EventCart.user_id == user['id'],
                    EventCart.user_type == user['user_type'],
                    EventCart.deleted_at.is_(None),
                    EventCart.is_cart_processed==False
                )
                
                if event_carts.count() == 0:
                    raise GenericError(status_code=404, exc="Event cart is empty")
    
                # Calculate total amount from all cart items
                event_carts = event_carts.all()
                total_amount = sum([cart.total_amount for cart in event_carts])
                transaction_batch_id = self._generate_transaction_id(user['id'])
                
                # Initialize discount variables
                discount_coupon_id = None
                calculated_coupon_discount = 0
                
                
    
                # Apply discount coupon if provided and valid
                if request.discount_coupon_code:
                    # Current date for comparison
                    current_date = datetime.now().date()
                    
                    discount_coupon_instance = db.query(DiscountCoupon).filter(
                        DiscountCoupon.code == request.discount_coupon_code,
                        DiscountCoupon.status == True,
                        DiscountCoupon.start_date <= datetime.now().date(),
                        DiscountCoupon.end_date >= datetime.now().date(),
                        DiscountCoupon.is_applicable_for_event == True
                    ).first()
                    
                    if discount_coupon_instance:
                        total_amount,calculated_coupon_discount = self.apply_discount(total_amount, discount_coupon_instance,user['id'])

                if request.charity:
                    # add 5% to cart_total_amount
                    total_amount = total_amount + (total_amount * 5 / 100)
                # Generate Stripe payment link if amount > 0, else mark as paid

                # Fetch wallet amount if use_wallet is enabled
                if request.use_wallet:
                    if user['user_type'] == 'parent':
                        wallet_amount = db.query(Parent.wallet_amount).filter(Parent.id == user['id']).scalar() or 0
                    elif user['user_type'] == 'participant':
                        wallet_amount = db.query(Participant.wallet_amount).filter(Participant.id == user['id']).scalar() or 0
                    # Deduct wallet amount from total
                    used_wallet_amount = min(wallet_amount, total_amount)
                    total_amount -= used_wallet_amount

                if total_amount > 0:
                    session = self._generate_event_payment_link(event_carts, total_amount, transaction_batch_id)
                    session_url = session.url
                else:
                    payment_status = 'success'
                    paid_on = datetime.now(timezone.utc)
                    is_payment_completed = True
                    
                    
        
                    
    
                # Create payment history record
                payment = EventPayment(
                    user_id=user['id'],
                    user_type=user['user_type'],
                    stripe_url=session_url,
                    total_amount=total_amount,
                    payment_status=payment_status,
                    payment_method="stripe" if total_amount > 0 else "wallet",
                    paid_on=paid_on,
                    is_payment_completed=is_payment_completed,
                    transaction_batch_id=transaction_batch_id,
                    coupon_discounted_amount=calculated_coupon_discount,
                    discount_coupon_id=discount_coupon_id,
                    used_wallet_amount=used_wallet_amount,
                )
                db.add(payment)
                
                # Update all event carts with transaction_batch_id and mark as processed
                for cart in event_carts:
                    cart.transaction_batch_id = transaction_batch_id
                    cart.is_cart_processed = True
                
                db.commit()
                if used_wallet_amount > 0 and payment_status == 'success':
                    # Update wallet amount
                    if user['user_type'] == 'parent':
                        db.query(Parent).filter(Parent.id == user['id']).update({
                            Parent.wallet_amount: Parent.wallet_amount - used_wallet_amount
                        })
                    elif user['user_type'] == 'participant':
                        db.query(Participant).filter(Participant.id == user['id']).update({
                            Participant.wallet_amount: Participant.wallet_amount - used_wallet_amount
                        })
                        # Add wallet history record
                        db.add(WalletHistory(
                            event_payment_id=payment.id,
                            user_type=payment.user_type,
                            refund_for_id=payment.user_id,
                            amount=used_wallet_amount,
                            refund_type="purchase",
                            transaction_id=payment.transaction_batch_id,
                            note="N/A",
                            is_credit=False,
                            message=f"Purchased event(s)",

                        ))
                db.commit()  # Add this line to commit wallet changes
                return {
                    "payment_url": session_url,
                    "payment_status": payment_status,
                    "is_payment_completed": is_payment_completed,
                    "transaction_batch_id": transaction_batch_id,
                    "success_url": f"{os.getenv('APP_UI_URL')}/dashboard/event-success/{transaction_batch_id}",
                    "cancel_url": f"{os.getenv('APP_UI_URL')}/dashboard/event-failed/{transaction_batch_id}",
                    "wallet_amount": wallet_amount,
                    "used_wallet_amount": used_wallet_amount,
                }
            except Exception as e:
                db.rollback()
                raise GenericError(status_code=422, exc=f"Could not create event payment session: {str(e)}")

    def _generate_transaction_id(self, user_id):
        """
        Generates a unique transaction ID for payment tracking.
        
        Args:
            user_id: ID of the user making the payment
            
        Returns:
            String containing timestamp, user_id and event identifier
        """
        timestamp = int(time.time() * 1000)
        base_id = str(timestamp)[-8:]
        return f"{base_id}-{user_id}-evnt"

    def _generate_event_payment_link(self, event_carts, total_amount, transaction_batch_id):
        """
        Creates a Stripe checkout session for event payments.
        
        Args:
            event_carts: List of EventCart objects
            total_amount: Total payment amount in currency units
            transaction_batch_id: Unique transaction identifier
            
        Returns:
            Stripe Session object containing payment URL
        """
        description = ",".join([cart.event.name for cart in event_carts])
        line_items = [
            {
                "quantity": 1,
                "price_data": {
                    "currency": self.currency,
                    "unit_amount": int(total_amount * 100),
                    "product_data": {
                        "name": "WAC Event Purchase",
                        "description": f"Event(s): {description}"
                    }
                }
            }
        ]
        
        stripe.api_key = settings.stripe_secret_key
        session= stripe.checkout.Session.create(
            payment_method_types=["card"],
            line_items=line_items,
            mode="payment",
            metadata={"transaction_batch_id": transaction_batch_id},
            success_url=f"{os.getenv('APP_UI_URL')}/dashboard/event-success/{transaction_batch_id}",
            cancel_url=f"{os.getenv('APP_UI_URL')}/dashboard/event-failed/{transaction_batch_id}",
        )
        if session.url:
            webhook_url = f"{settings.stripe_webhook_url}/event/stripe/webhook"
            existing_webhooks = stripe.WebhookEndpoint.list()
            is_webhook_exists = False
            for webhook in existing_webhooks:
                if webhook.url == webhook_url:
                    is_webhook_exists = True
                    break
            print(is_webhook_exists)
            if not is_webhook_exists:
                self.create_webhook(stripe, webhook_url)
        return session

    
    def create_webhook(self, stripe, webhook_url):
        print("creating webhook")
        webhook = stripe.WebhookEndpoint.create(
            url=webhook_url,
            enabled_events=["payment_intent.succeeded",
                            "checkout.session.completed",
                            "payment_intent.payment_failed", ], )

        return webhook
    

    def update_payment(self, payload: str):
        """
        Updates payment status and handles wallet deduction if payment was completed using wallet.
        
        Args:
            payload: Payment payload containing transaction details
            
        Returns:
            Updated payment record
        """
        with get_db() as db:
            event = json.loads(payload.decode("utf-8"))

            if event["type"] == "checkout.session.completed":
                transaction_batch_id = event["data"]["object"]["metadata"]["transaction_batch_id"]
                print(transaction_batch_id)
                if event["data"]["object"]["payment_status"] == "paid":
                    payment = db.query(EventPayment).filter(
                        EventPayment.transaction_batch_id == event["data"]["object"]["metadata"]["transaction_batch_id"]
                    ).first()
            
                    if not payment:
                        raise GenericError( status_code=404,exc="Payment not found")
                
                    if payment.used_wallet_amount and payment.used_wallet_amount > 0:
                        if payment.user_type == "parent":
                            parent = db.query(Parent).filter(Parent.id == payment.user_id).first()
                            if parent:
                                parent.wallet_amount -= payment.used_wallet_amount
                        elif payment.user_type == "participant":
                            participant = db.query(Participant).filter(Participant.id == payment.user_id).first()
                            if participant:
                                participant.wallet_amount -= payment.used_wallet_amount
                        
                    db.query(EventPayment).filter(EventPayment.transaction_batch_id == transaction_batch_id).update(
                        {
                            "payment_status": "success",
                            "payment_id": event["data"]["object"]["payment_intent"],
                            "is_payment_completed": True,
                            "paid_on": datetime.now()
                        }, synchronize_session="fetch"
                    )
                    
                    db.query(EventCart).filter(EventCart.transaction_batch_id == transaction_batch_id).update(
                        {"is_cart_processed": True}
                    )
                    
                    # Send email notification for successful event purchase
                    payment = db.query(EventPayment).filter(
                        EventPayment.transaction_batch_id == transaction_batch_id,EventPayment.is_payment_completed==True
                    ).first()
                    if payment:
                        send_event_order_summary_email(payment.id)
                    
                    if payment.used_wallet_amount and payment.used_wallet_amount > 0:
                        db.add(WalletHistory(
                            event_payment_id=payment.id,
                            user_type=payment.user_type,
                            refund_for_id=payment.user_id,
                            amount=payment.used_wallet_amount,
                            refund_type="purchase",
                            transaction_id=payment.transaction_batch_id,
                            note="N/A",
                            is_credit=False,
                            message=f"Purchased event(s)",

                        ))
                else:
                    db.query(EventPayment).filter(EventPayment.transaction_batch_id == transaction_batch_id).update(
                        {
                            "payment_status": "failed",
                            "is_payment_completed": False
                        }, synchronize_session="fetch"
                    )
                db.commit()
         
            
            

    def get_all_event_bookings(self):
        """
        Retrieves all event bookings for admin view
        Returns:
            List[AdminEventBookingResponse]: List of booking details
        """
        with get_db() as db:
            payments = db.query(EventPayment).all()
            
            result = []
            for payment in payments:
                carts = db.query(EventCart).join(
                    Event, EventCart.event_id == Event.id
                ).filter(
                    EventCart.transaction_batch_id == payment.transaction_batch_id,
                    EventCart.is_cart_processed == True
                ).all()
                
                for cart in carts:
                    user_details = {}
                    if payment.user_type == "parent":
                        parent = db.query(Parent).filter(Parent.id == payment.user_id).first()
                        if parent:
                            user_details = {
                                'first_name': parent.first_name,
                                'last_name': parent.last_name,
                                'email': parent.email,
                               
                                
                            }
                    elif payment.user_type == "participant":
                        participant = db.query(Participant).filter(Participant.id == payment.user_id).first()
                        if participant:
                            user_details = {
                                
                                'first_name': participant.first_name,
                                'last_name': participant.last_name,
                                'email': participant.email,
                                
                            }
                    
                    result.append({
                        'id': payment.id,
                        'transaction_batch_id': payment.transaction_batch_id,
                        **user_details,  # Merge user details directly into response
                        'event_id': cart.event_id,
                        'event_name': cart.event.name,
                        'event_date': cart.event.event_start_date,
                        'participants_count': cart.no_of_participants,
                        'total_amount': payment.total_amount,
                        'payment_method': payment.payment_method,
                        'payment_date': payment.paid_on,
                        'used_wallet_amount': payment.used_wallet_amount,
                        'discount_amount': payment.coupon_discounted_amount
                    })
            return result


    def get_user_event_purchases(self, user):
        """
        Retrieves all event purchases for the current user.
        Returns:
            List[EventPurchaseDetailResponse]: List of purchase details
        """
        with get_db() as db:
            payments = db.query(EventPayment).filter(
                EventPayment.user_id == user['id'],
                EventPayment.user_type == user['user_type'],
            
            ).all()
            
            result = []
            for payment in payments:
                carts = db.query(EventCart).join(
                    Event, EventCart.event_id == Event.id
                ).filter(
                    EventCart.transaction_batch_id == payment.transaction_batch_id,
                    EventCart.is_cart_processed == True
                ).all()
                
                for cart in carts:
                    result.append({
                        'transaction_id': payment.transaction_batch_id,
                        'event_id': cart.event_id,
                        'event_name': cart.event.name,
                        'event_date': cart.event.event_start_date,
                        'participants_count': cart.no_of_participants,
                        'total_amount': payment.total_amount,
                        'payment_method': payment.payment_method,
                        'payment_date': payment.paid_on,
                        'used_wallet_amount': payment.used_wallet_amount,
                        'discount_amount': payment.coupon_discounted_amount,
                        'is_payment_completed': payment.is_payment_completed,
                        'payment_status': payment.payment_status
                    })
            
            return result
