import os
import stripe
import logging
from datetime import timedelta, datetime
from pymongo.collection import Collection
from app.db import database
from ...models.saas.subscriptions import (
    SubscriptionTypeCreate, SubscriptionType, SubscriptionCreate, 
    Subscription, PaymentCreate, Payment, SubscriptionUpdate, SubscriptionLog
)
from ...models.saas.usersmodel import User
from app.v1.libraries.object import str_to_objectid
from ...libraries.stripe import create_checkout_session, update_stripe_subscription, verify_stripe_signature
from ...libraries.email_templates import (
    send_subscription_success_email, send_payment_success_email, 
    send_payment_failure_email, send_subscription_cancelled_email
)

logger = logging.getLogger(__name__)

# Collection constants
SUBSCRIPTION_TYPE_COLLECTION = "subscription_types"
SUBSCRIPTION_COLLECTION = "subscriptions"
PAYMENTS_COLLECTION = "payments"
ACCOUNTS_COLLECTION = "accounts"
LOG_SUBSCRIPTION_COLLECTION = "subscription_logs"
MY_APP_DOMAIN = os.getenv('MY_APP_DOMAIN')

# ----- Subscription Type Endpoints -----

async def create_subscription_type_service(
    subscription_type: SubscriptionTypeCreate, 
    db: database.MongoDB
) -> SubscriptionType:
    collection: Collection = db[SUBSCRIPTION_TYPE_COLLECTION]
    result = collection.insert_one(subscription_type.dict())
    return SubscriptionType(**subscription_type.dict(), subscription_type_id=str(result.inserted_id))

async def list_subscription_types_service(db: database.MongoDB) -> list:
    collection: Collection = db[SUBSCRIPTION_TYPE_COLLECTION]
    subscription_types = list(collection.find().sort("type", 1))
    for st in subscription_types:
        st['subscription_type_id'] = st.pop('type')
    return [SubscriptionType(**st) for st in subscription_types]

# ----- Subscription Endpoints -----

async def get_subscription_service(subscription_id: str, db: database.MongoDB) -> Subscription:
    subscription_collection: Collection = db[SUBSCRIPTION_COLLECTION]
    subscription_dict = subscription_collection.find_one({'_id': str_to_objectid(subscription_id)})
    if not subscription_dict:
        raise ValueError("Subscription not found")
    subscription_dict['subscription_id'] = str(subscription_dict['_id'])
    return Subscription(**subscription_dict)

async def log_subscription_change_service(subscription_log: SubscriptionLog, db: database.MongoDB) -> str:
    log_collection: Collection = db[LOG_SUBSCRIPTION_COLLECTION]
    result = log_collection.insert_one(subscription_log.dict())
    log_id = str(result.inserted_id)
    logger.info("Logged subscription change with id: %s", log_id)
    return log_id

async def create_subscription_service(
    subscription: SubscriptionCreate, 
    db: database.MongoDB, 
    current_user: User
) -> dict:
    subscription_collection: Collection = db[SUBSCRIPTION_COLLECTION]
    account_collection: Collection = db[ACCOUNTS_COLLECTION]
    
    if current_user['roles'] > 2:
        raise ValueError("You are not permitted to create a subscription.")
    
    account = account_collection.find_one({"_id": str_to_objectid(current_user["account_id"])})
    subscription_dict = subscription.dict()
    subscription_dict['start_date'] = datetime.utcnow()
    subscription_dict['renew_start_date'] = subscription_dict['start_date']
    subscription_dict['renew_end_date'] = subscription_dict['start_date'] + timedelta(days=30)
    
    existing_subscription = subscription_collection.find_one({"account_id": current_user["account_id"]})
    if existing_subscription:
        subscription_id = existing_subscription["_id"]
        subscription_collection.update_one({"_id": subscription_id}, {"$set": subscription_dict})
    else:
        subscription_dict['status'] = "new"
        result = subscription_collection.insert_one(subscription_dict)
        subscription_id = result.inserted_id
    
    created_subscription_dict = subscription_collection.find_one({"_id": subscription_id})
    created_subscription = Subscription(**created_subscription_dict, subscription_id=str(subscription_id))
    log_id = await log_subscription_change_service(
        SubscriptionLog(**created_subscription.dict(), updated_user_id=str(current_user["_id"]), updated_date=datetime.utcnow()),
        db
    )
    
    account_collection.update_one(
        {'user_id': created_subscription.user_id},
        {'$set': {
            'active_subscription_id': str(subscription_id),
            'subscription_status': created_subscription.status,
            'account_type': created_subscription.subscription_type_id
        }}
    )
    
    subscription_type_doc = db[SUBSCRIPTION_TYPE_COLLECTION].find_one({"type": subscription.subscription_type_id})
    stripe_price_id = subscription_type_doc.get("stripe_price_id")
    
    checkout_session_response = await create_checkout_session(
        account_id=current_user["account_id"],
        account_name=account["account_name"],
        email=account["email"],
        subscription_id=str(subscription_id),
        subscription_type_id=created_subscription.subscription_type_id,
        stripe_price_id=stripe_price_id,
        quantity=created_subscription.subscription_agents_count,
        log_id=str(log_id),
    )
    
    return {
        "subscription": created_subscription,
        "checkout_url": checkout_session_response.get("url", "")
    }

async def update_subscription_service(
    subscription: SubscriptionUpdate, 
    db: database.MongoDB, 
    current_user: User
) -> dict:
    subscription_dict = subscription.dict()
    subscription_id = subscription_dict["subscription_id"]
    
    if not subscription_id:
        raise ValueError("Subscription ID is required.")
    if current_user["roles"] > 2:
        raise ValueError("You are not permitted to update a subscription.")
    
    subscription_collection: Collection = db[SUBSCRIPTION_COLLECTION]
    account_collection: Collection = db[ACCOUNTS_COLLECTION]
    
    existing_subscription = subscription_collection.find_one({"_id": str_to_objectid(subscription_id)})
    if not existing_subscription or existing_subscription.get('account_id') != current_user["account_id"]:
        raise ValueError("Subscription not found or access denied")
    
    updated_data = subscription.dict(exclude_none=True)
    if 'subscription_agents_count' in updated_data and 'peragent_cost' in updated_data:
        updated_data['total_cost'] = updated_data['subscription_agents_count'] * updated_data['peragent_cost']
    
    subscription_type_doc = db[SUBSCRIPTION_TYPE_COLLECTION].find_one({"type": updated_data['subscription_type_id']})
    stripe_price_id = subscription_type_doc.get("stripe_price_id")
    
    if existing_subscription.get('stripe_subscription_id'):
        stripe_updated = await update_stripe_subscription(
            subscription_id=existing_subscription['stripe_subscription_id'],
            new_price_id=stripe_price_id,
            new_quantity=updated_data['subscription_agents_count']
        )
        if stripe_updated:
            subscription_collection.update_one({'_id': str_to_objectid(subscription_id)}, {'$set': updated_data})
            updated_subscription = subscription_collection.find_one({'_id': str_to_objectid(subscription_id)})
            await log_subscription_change_service(
                SubscriptionLog(**updated_subscription, updated_user_id=str(current_user["_id"]), updated_date=datetime.utcnow()),
                db
            )
        else:
            raise ValueError("Stripe subscription update failed")
        checkout_url = None
    else:
        account = account_collection.find_one({"_id": str_to_objectid(current_user["account_id"])})
        subscription_collection.update_one({'_id': str_to_objectid(subscription_id)}, {'$set': updated_data})
        updated_subscription = subscription_collection.find_one({'_id': str_to_objectid(subscription_id)})
        await log_subscription_change_service(
            SubscriptionLog(**updated_subscription, updated_user_id=str(current_user["_id"]), updated_date=datetime.utcnow()),
            db
        )
        checkout_session_response = await create_checkout_session(
            account_id=current_user["account_id"],
            account_name=account["name"],
            email=current_user["email"],
            subscription_id=subscription_id,
            subscription_type_id=updated_data['subscription_type_id'],
            stripe_price_id=stripe_price_id,
            quantity=updated_data['subscription_agents_count'],
            log_id=""
        )
        checkout_url = checkout_session_response.get("url")
    
    if 'subscription_type_id' in updated_data:
        account_collection.update_one(
            {'_id': str_to_objectid(existing_subscription['account_id'])},
            {'$set': {'account_type': updated_data['subscription_type_id']}}
        )
    
    updated_subscription["subscription_id"] = str(updated_subscription["_id"])
    updated_subscription["_id"] = str(updated_subscription["_id"])
    
    return {"subscription": updated_subscription, "checkout_url": checkout_url}

async def cancel_subscription_service(
    subscription_id: str, 
    db: database.MongoDB, 
    current_user: User
) -> Subscription:
    subscription_collection: Collection = db[SUBSCRIPTION_COLLECTION]
    subscription = subscription_collection.find_one({'_id': str_to_objectid(subscription_id)})
    if not subscription or subscription['user_id'] != current_user.user_id:
        raise ValueError("Subscription not found or access denied.")
    subscription_collection.update_one({'_id': str_to_objectid(subscription_id)}, {'$set': {'status': 'cancelled'}})
    account_collection: Collection = db[ACCOUNTS_COLLECTION]
    account_collection.update_one(
        {'user_id': current_user.user_id}, 
        {'$set': {'active_subscription_id': None, 'subscription_status': 'cancelled'}}
    )
    updated_subscription = subscription_collection.find_one({'_id': str_to_objectid(subscription_id)})
    return Subscription(**updated_subscription)

async def list_subscriptions_service(db: database.MongoDB) -> list:
    collection: Collection = db[SUBSCRIPTION_COLLECTION]
    subscriptions = list(collection.find())
    return [Subscription(**s) for s in subscriptions]

async def list_payments_service(db: database.MongoDB, current_user: User) -> dict:
    account_id = current_user["account_id"]
    if current_user["roles"] > 2:
        raise ValueError("Not permitted to view these payments.")
    collection: Collection = db[PAYMENTS_COLLECTION]
    payments_query = {"account_id": account_id}
    payments = list(collection.find(payments_query))
    for payment in payments:
        payment["payment_id"] = str(payment["_id"])
    total_count = len(payments)
    return {"payments": payments, "total_count": total_count}

# ----- Payment and Stripe Helpers -----

async def create_payment_service(payment: PaymentCreate, db: database.MongoDB):
    collection: Collection = db[PAYMENTS_COLLECTION]
    result = collection.insert_one(payment.dict())
    return result

async def update_subscription_status_service(
    subscription_id: str, stripe_subscription_id: str, status: str, db: database.MongoDB
) -> bool:
    subscription_collection: Collection = db[SUBSCRIPTION_COLLECTION]
    result = subscription_collection.update_one(
        {"_id": str_to_objectid(subscription_id)},
        {"$set": {"status": status, "stripe_subscription_id": stripe_subscription_id}}
    )
    logger.info("Stripe ID updated to %s", stripe_subscription_id)
    return result.modified_count > 0

async def update_subscription_stripe_service(
    stripe_subscription_id: str, status: str, db: database.MongoDB
) -> bool:
    subscription_collection: Collection = db[SUBSCRIPTION_COLLECTION]
    subscription = subscription_collection.find_one({"stripe_subscription_id": stripe_subscription_id})
    if not subscription:
        logger.error("Subscription not found for Stripe ID: %s", stripe_subscription_id)
        return False
    account_id = subscription['account_id']
    result = subscription_collection.update_one(
        {"stripe_subscription_id": stripe_subscription_id},
        {"$set": {"status": status}}
    )
    if result.modified_count > 0:
        await update_account_status_service(account_id, status, db)
        log_collection: Collection = db["subscription_logs"]
        latest_log = log_collection.find_one(
            {"subscription_id": str(subscription['_id'])}, 
            sort=[('updated_date', -1)]
        )
        if latest_log:
            await update_log_status_service(str(latest_log['_id']), stripe_subscription_id, status, db)
    return result.modified_count > 0

async def update_account_status_service(account_id: str, status: str, db: database.MongoDB) -> bool:
    account_collection: Collection = db[ACCOUNTS_COLLECTION]
    result = account_collection.update_one(
        {"_id": str_to_objectid(account_id)},
        {"$set": {"subscription_status": status}}
    )
    return result.modified_count > 0

async def update_log_status_service(log_id: str, stripe_subscription_id: str, status: str, db: database.MongoDB) -> bool:
    log_collection: Collection = db[LOG_SUBSCRIPTION_COLLECTION]
    result = log_collection.update_one(
        {"_id": str_to_objectid(log_id)},
        {"$set": {"stripe_subscription_id": stripe_subscription_id, "status": status}}
    )
    return result.modified_count > 0

# ----- Stripe Webhook and Portal Functions -----

async def stripe_webhook_service(request, background_tasks, db: database.MongoDB):
    event = await verify_stripe_signature(request)
    logger.info("Received event: %s", event["type"])
    stripe_obj = event['data']['object']
    
    if event['type'] == 'checkout.session.completed':
        setstatus = 'active'
        await handle_checkout_session_completed_service(stripe_obj, setstatus, db, background_tasks)
    elif event['type'] in ['customer.subscription.created', 'customer.subscription.updated']:
        await handle_subscription_update_service(stripe_obj, db)
    elif event['type'] in ['customer.subscription.deleted']:
        await handle_payment_payment_failed_service(stripe_obj, 'cancelled', db)
        invoice = event['data']['object']
        user_email = invoice['customer_email']
        customer = stripe.Customer.retrieve(invoice['customer'])
        customer_name = customer.get("name", "Valued Customer")
        background_tasks.add_task(send_payment_failure_email, email=user_email, name=customer_name, amount=invoice['amount_due'])
    elif event['type'] == 'payment.payment_failed':
        setstatus = 'cancelled'
        await handle_payment_payment_failed_service(stripe_obj, setstatus, db)
    elif event['type'] == 'invoice.payment_failed':
        await handle_payment_payment_failed_service(stripe_obj, 'hold', db)
        invoice = event['data']['object']
        user_email = invoice['customer_email']
        customer = stripe.Customer.retrieve(invoice['customer'])
        customer_name = customer.get("name", "Valued Customer")
        background_tasks.add_task(send_payment_failure_email, email=user_email, name=customer_name, amount=invoice['amount_due'])
    return {"status": "success"}

async def handle_checkout_session_completed_service(session_obj, setstatus, db: database.MongoDB, background_tasks):
    metadata = session_obj.get('metadata', {})
    local_subscription_id = metadata.get('subscription_id')
    account_id = metadata.get('account_id')
    account_name = metadata.get('account_name')
    email = metadata.get('email')
    stripe_subscription_id = session_obj.get('subscription')
    amount_total = session_obj.get('amount_total') / 100
    stripe_transaction_id = session_obj.get('payment_intent')
    log_id = metadata.get('log_id')
    agent_count = metadata.get('quantity')
    
    if local_subscription_id:
        stripe_subscription_id = session_obj.get('subscription')
        if stripe_subscription_id:
            await update_subscription_status_service(local_subscription_id, stripe_subscription_id, setstatus, db)
            await update_account_status_service(account_id, setstatus, db)
            await update_log_status_service(log_id, stripe_subscription_id, setstatus, db)
            payment_data = PaymentCreate(
                subscription_id=local_subscription_id,
                stripe_subscription_id=stripe_subscription_id,
                stripe_transaction_id=stripe_transaction_id,
                account_id=account_id,
                amount=amount_total,
                due_date=datetime.utcnow(),
                status="paid"
            )
            await create_payment_service(payment_data, db)
            background_tasks.add_task(send_subscription_success_email, email=email, name=account_name, count=agent_count, amount=amount_total)
            logger.info("Updated local subscription %s with Stripe ID %s and set status to active.", local_subscription_id, stripe_subscription_id)

async def handle_subscription_update_service(subscription_obj, db: database.MongoDB):
    stripe_subscription_id = subscription_obj['id']
    status = subscription_obj['status']
    new_status = 'active' if status == 'active' else 'hold'
    await update_subscription_stripe_service(stripe_subscription_id, new_status, db)
    logger.info("Subscription %s updated to %s.", stripe_subscription_id, new_status)

async def handle_payment_payment_failed_service(payment_obj, setstatus, db: database.MongoDB):
    stripe_subscription_id = payment_obj['subscription']
    await update_subscription_stripe_service(stripe_subscription_id, setstatus, db)
    logger.info("Payment failed for subscription %s. Set to cancelled.", stripe_subscription_id)

async def verify_payment_service(session_id: str) -> dict:
    try:
        session = stripe.checkout.Session.retrieve(session_id)
        if session.payment_status == "paid":
            return {"success": True, "message": "Payment verified successfully.", "status": "active"}
        else:
            return {"success": False, "message": "Payment verification failed."}
    except Exception as e:
        logger.error("Error verifying payment session: %s", e)
        raise e

async def create_portal_session_service(subscription_id: str, db: database.MongoDB) -> dict:
    subscription_collection = db[SUBSCRIPTION_COLLECTION]
    subscription = subscription_collection.find_one({"_id": str_to_objectid(subscription_id)})
    if not subscription or "stripe_subscription_id" not in subscription:
        raise ValueError("Stripe Subscription ID not found for the provided subscription ID.")
    
    stripe_subscription_id = subscription["stripe_subscription_id"]
    try:
        subscription_details = stripe.Subscription.retrieve(stripe_subscription_id)
        customer_id = subscription_details.customer
    except stripe.error.StripeError as e:
        raise ValueError(f"Stripe API error: {str(e)}")
    
    session = stripe.billing_portal.Session.create(
        customer=customer_id,
        return_url=f"{MY_APP_DOMAIN}/billing",
    )
    return {"url": session.url}
