File size: 19,808 Bytes
5de75b9
fc1cb5f
3867d94
22e43d6
3867d94
 
 
b91dd33
 
3cd94a3
b91dd33
 
3867d94
3cd94a3
3867d94
 
 
22e43d6
 
 
f684c6d
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5de75b9
3867d94
 
40b8c2c
3867d94
 
5de75b9
bdbd9f4
 
 
 
 
 
3867d94
fc1cb5f
4b85c5e
fc1cb5f
 
4b85c5e
fc1cb5f
 
 
 
 
 
 
 
 
4b85c5e
fc1cb5f
4b85c5e
fc1cb5f
3867d94
fc1cb5f
3cd94a3
85625af
3cd94a3
85625af
d778cc5
85625af
5d0fde1
85625af
 
 
 
 
 
3867d94
5de75b9
22e43d6
 
7e3fef8
5de75b9
3867d94
 
bdbd9f4
7472f24
fc1cb5f
 
 
 
7472f24
3771094
5de75b9
3867d94
5de75b9
3867d94
 
22e43d6
3cd94a3
85625af
40b8c2c
31e24b7
 
 
 
6384b49
 
 
 
 
5d0fde1
 
 
 
 
 
 
 
 
fc1cb5f
3867d94
fc1cb5f
3867d94
3771094
3867d94
5de75b9
22e43d6
 
fc1cb5f
22e43d6
fc1cb5f
3867d94
fc1cb5f
3771094
3867d94
5de75b9
22e43d6
 
fc1cb5f
22e43d6
fc1cb5f
3867d94
fc1cb5f
3771094
22e43d6
5de75b9
22e43d6
fc1cb5f
22e43d6
fc1cb5f
3771094
22e43d6
5de75b9
22e43d6
fc1cb5f
22e43d6
fc1cb5f
3771094
22e43d6
5de75b9
22e43d6
fc1cb5f
22e43d6
fc1cb5f
3771094
b91dd33
 
 
 
 
 
 
 
3218196
 
 
 
 
 
 
 
3cd94a3
 
d778cc5
85625af
5d0fde1
 
 
85625af
 
3218196
 
 
 
 
 
 
 
 
3cd94a3
3218196
3cd94a3
7472f24
fc1cb5f
7472f24
3771094
3867d94
3771094
5de75b9
7472f24
fc1cb5f
7472f24
3771094
3867d94
22e43d6
 
 
fc1cb5f
24e427f
 
3771094
22e43d6
3867d94
fc1cb5f
3771094
22e43d6
 
fc1cb5f
3771094
fc1cb5f
3867d94
fc1cb5f
3867d94
22e43d6
5de75b9
3867d94
 
fc1cb5f
 
3867d94
 
 
b91dd33
 
 
 
 
6d4c913
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3867d94
 
 
 
6d4c913
3867d94
22e43d6
3867d94
 
 
 
 
5de75b9
3867d94
 
5de75b9
3867d94
 
 
 
 
 
 
 
bdbd9f4
 
3867d94
 
 
 
22e43d6
3867d94
0a10df7
5de75b9
3867d94
 
 
 
 
22e43d6
5de75b9
fc1cb5f
5de75b9
fc1cb5f
22e43d6
fc1cb5f
3867d94
3771094
2f4b429
 
 
fc1cb5f
4b85c5e
 
2f4b429
 
 
fc1cb5f
4b85c5e
 
2f4b429
 
 
 
 
 
 
 
fc1cb5f
2f4b429
4b85c5e
2f4b429
4b85c5e
2f4b429
fc1cb5f
a05d009
 
 
2644c0e
 
a05d009
 
 
fc1cb5f
 
 
3771094
610766f
3771094
522ee13
31e24b7
40b8c2c
5596926
31e24b7
20f7041
3cd94a3
9bf7efd
3771094
 
610766f
3771094
31e24b7
 
40b8c2c
5b7da00
31e24b7
20f7041
3cd94a3
9bf7efd
6384b49
3ee4cc7
 
 
 
 
6384b49
 
 
 
3ee4cc7
 
 
 
 
 
 
 
 
 
c60d91c
 
 
 
 
35f9ee5
20f7041
3cd94a3
9bf7efd
22e43d6
f12a5d7
 
 
 
 
afe8cfd
f12a5d7
afe8cfd
f12a5d7
afe8cfd
f12a5d7
 
 
fc1cb5f
 
 
22e43d6
 
5de75b9
3867d94
 
 
bdbd9f4
22e43d6
 
 
 
 
 
24e427f
22e43d6
3771094
22e43d6
 
 
 
 
24e427f
22e43d6
3771094
22e43d6
 
 
24e427f
22e43d6
3771094
22e43d6
 
 
fc1cb5f
 
22e43d6
fc1cb5f
 
 
 
22e43d6
 
 
 
 
 
 
 
5de75b9
22e43d6
 
 
 
 
3867d94
 
 
bdbd9f4
3867d94
87a7da1
 
 
fc1cb5f
87a7da1
 
 
 
 
 
 
fc1cb5f
87a7da1
 
 
 
 
 
 
 
 
3867d94
 
22e43d6
3867d94
 
bdbd9f4
3867d94
 
22e43d6
5de75b9
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
# app/main.py
# Lojiz Platform + Aida AI - Graph-Based Architecture (v1 Primary)

from fastapi import FastAPI, Request
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import JSONResponse
from fastapi.exceptions import RequestValidationError
from slowapi import _rate_limit_exceeded_handler
from slowapi.errors import RateLimitExceeded
from slowapi.errors import RateLimitExceeded
from slowapi.middleware import SlowAPIMiddleware
from app.middleware import limiter, rate_limit_exceeded_handler
from contextlib import asynccontextmanager
from apscheduler.schedulers.asyncio import AsyncIOScheduler
import logging
import os

logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)

# ============================================================
# SENTRY INTEGRATION - Error Monitoring
# ============================================================
import sentry_sdk
from sentry_sdk.integrations.fastapi import FastApiIntegration
from sentry_sdk.integrations.starlette import StarletteIntegration

def init_sentry():
    """Initialize Sentry for production error monitoring"""
    sentry_dsn = os.getenv("SENTRY_DSN", "")
    environment = os.getenv("ENVIRONMENT", "development")
    
    if sentry_dsn and environment == "production":
        sentry_sdk.init(
            dsn=sentry_dsn,
            environment=environment,
            integrations=[
                FastApiIntegration(transaction_style="endpoint"),
                StarletteIntegration(transaction_style="endpoint"),
            ],
            # Performance monitoring
            traces_sample_rate=0.2,  # 20% of transactions
            # Error sampling
            profiles_sample_rate=0.1,  # 10% of profiled transactions
            # Don't send PII
            send_default_pii=False,
            # Release version
            release=os.getenv("APP_VERSION", "1.0.0"),
        )
        logger.info("✅ Sentry initialized for production monitoring")
    elif sentry_dsn:
        logger.info(f"⚠️ Sentry DSN set but environment is {environment} - not initializing")
    else:
        logger.info("ℹ️ Sentry DSN not configured - error monitoring disabled")

# Initialize Sentry before app startup
init_sentry()

# CORE IMPORTS
try:
    from app.config import settings
    from app.database import connect_db, disconnect_db, ensure_indexes as ensure_auth_indexes, ensure_review_indexes
    from app.routes import auth
except ImportError as e:
    logger.error(f"Core import error: {e}")
    raise

try:
    from app.core.exceptions import AuthException
except ImportError:
    AuthException = Exception

# ============================================================
# AI IMPORTS - GRAPH-BASED ARCHITECTURE
# ============================================================
try:
    from app.ai.routes.chat import router as ai_chat_router
    from app.ai.config import (
        validate_ai_startup,
        check_redis_health,
        check_qdrant_health,
        redis_client,
        qdrant_client,
    )
    from app.ai.memory.redis_context_memory import get_memory_manager
    from app.ml.models.ml_listing_extractor import get_ml_extractor
    logger.info("✅ Graph-based AI architecture loaded")
except ImportError as e:
    logger.error(f"AI import error: {e}")
    raise

from app.models.listing import ensure_listing_indexes
from app.models.booking import ensure_booking_indexes
from app.models.viewing import ensure_viewing_indexes
from app.jobs.payout_jobs import process_escrow_payouts
from app.jobs.stay_notification_jobs import (
    dispatch_booking_status_transitions,
    dispatch_checkin_reminders,
    dispatch_checkout_reminders,
    dispatch_checkout_review_requests,
)
from app.jobs.viewing_notification_jobs import (
    dispatch_viewing_reminders,
    dispatch_post_viewing_followups,
)

# ENVIRONMENT
environment = os.getenv("ENVIRONMENT", "development")
is_production = environment == "production"

# LIFESPAN
@asynccontextmanager
async def lifespan(app: FastAPI):
    """Application lifespan - startup and shutdown"""
    logger.info("=" * 70)
    logger.info("🚀 Starting Lojiz Platform + Aida AI")
    logger.info("   Architecture: Graph-Based (State Machine + Validation)")
    logger.info("   Primary Endpoint: /ai/v1 (Graph-Based)")
    logger.info("   Fallback Endpoint: /ai/v2 (Legacy)")
    logger.info("=" * 70)

    # STARTUP
    try:
        logger.info("Connecting to MongoDB...")
        await connect_db()
        await ensure_auth_indexes()
        await ensure_listing_indexes()
        await ensure_booking_indexes()
        await ensure_viewing_indexes()
        await ensure_review_indexes()
        
        # Import and create chat indexes
        from app.database import ensure_chat_indexes
        await ensure_chat_indexes()

        # Quota indexes
        from app.models.quota import ensure_quota_indexes
        await ensure_quota_indexes()

        # Notification deduplication index — enforces exactly-once delivery
        from app.database import get_db as _get_db
        _ndb = await _get_db()
        await _ndb["aida_notification_log"].create_index(
            [("booking_id", 1), ("type", 1)],
            unique=True,
            name="notification_dedup",
        )

        logger.info("✅ MongoDB connected and indexed")
    except Exception as e:
        logger.critical(f"❌ MongoDB connection failed - aborting startup: {e}")
        raise

    try:
        logger.info("Connecting to Redis...")
        if redis_client:
            await redis_client.ping()
            logger.info("✅ Redis connected")
        else:
            logger.warning("⚠️ Redis not available (optional)")
    except Exception as e:
        logger.warning(f"⚠️ Redis connection failed (continuing without): {e}")

    try:
        logger.info("Connecting to Qdrant...")
        if qdrant_client:
            await qdrant_client.get_collections()
            logger.info("✅ Qdrant connected")
        else:
            logger.warning("⚠️ Qdrant not available (optional)")
    except Exception as e:
        logger.warning(f"⚠️ Qdrant connection failed (continuing without): {e}")

    try:
        logger.info("Validating AI components...")
        ai_checks = await validate_ai_startup()
        logger.info("✅ AI components validated")
    except Exception as e:
        logger.warning(f"⚠️ AI validation warning: {e}")

    try:
        logger.info("Initializing ML Extractor...")
        ml = get_ml_extractor()
        logger.info("✅ ML Extractor ready")
    except Exception as e:
        logger.warning(f"⚠️ ML Extractor initialization warning: {e}")

    try:
        logger.info("Initializing Memory Manager...")
        manager = get_memory_manager()
        logger.info("✅ Memory Manager ready")
    except Exception as e:
        logger.warning(f"⚠️ Memory Manager initialization warning: {e}")

    # Initialize Distributed Chat (Redis Pub/Sub)
    try:
        from app.routes.websocket_chat import chat_manager
        await chat_manager.initialize()
        logger.info("✅ Redis Pub/Sub for Chat initialized")
    except Exception as e:
        logger.warning(f"⚠️ Chat manager init failed: {e}")

    # Proactive alert indexes
    try:
        from app.services.proactive_service import ensure_proactive_indexes
        await ensure_proactive_indexes()
        logger.info("✅ Proactive notification indexes ensured")
    except Exception as e:
        logger.warning(f"⚠️ Proactive indexes setup failed (non-critical): {e}")

    # Initialize APScheduler for Background Jobs (Payouts & Escrow)
    scheduler = AsyncIOScheduler()
    scheduler.add_job(dispatch_booking_status_transitions, "interval", minutes=15)  # CONFIRMED→ACTIVE_STAY→COMPLETED
    scheduler.add_job(process_escrow_payouts, "interval", minutes=30)
    scheduler.add_job(dispatch_checkin_reminders, "interval", minutes=30)      # pre-check-in reminder (within 2h)
    scheduler.add_job(dispatch_checkout_reminders, "interval", minutes=30)     # 30-min pre-checkout reminder
    scheduler.add_job(dispatch_checkout_review_requests, "interval", minutes=30)   # post-checkout review request
    scheduler.add_job(dispatch_viewing_reminders, "interval", minutes=30)     # 3-hour pre-viewing reminder
    scheduler.add_job(dispatch_post_viewing_followups, "interval", minutes=30)    # post-viewing follow-up

    # Register proactive alert scheduler
    try:
        from app.tasks.proactive_scheduler import register_proactive_jobs
        register_proactive_jobs(scheduler)
        logger.info("✅ Proactive alert scheduler registered")
    except Exception as e:
        logger.warning(f"⚠️ Proactive scheduler registration failed (non-critical): {e}")

    scheduler.start()
    logger.info("✅ APScheduler started for Background Jobs (Payouts, Escrow, Proactive Alerts).")

    logger.info("=" * 70)
    logger.info("✅ APPLICATION READY - Graph-Based Architecture Active!")
    logger.info("=" * 70)

    yield

    # SHUTDOWN
    logger.info("=" * 70)
    logger.info("🛑 Shutting down Lojiz Platform + Aida AI")
    logger.info("=" * 70)

    try:
        try:
            ml = get_ml_extractor()
            ml.currency_mgr.clear_cache()
            logger.info("✅ ML caches cleared")
        except Exception as e:
            logger.debug(f"ML cache clear skipped: {e}")

        from app.database import disconnect_db
        await disconnect_db()
        logger.info("✅ MongoDB disconnected")

        if redis_client:
            await redis_client.close()
            logger.info("✅ Redis closed")

        logger.info("✅ Shutdown complete")
    except Exception as e:
        logger.warning(f"⚠️ Shutdown warning: {e}")


# FASTAPI SETUP
app = FastAPI(
    title="Lojiz Platform + Aida AI",
    description="Real-estate platform with conversational AI assistant (Graph-Based Architecture)",
    version="2.0.0",
    lifespan=lifespan,
)

# RATE LIMITING
app.state.limiter = limiter
app.add_exception_handler(RateLimitExceeded, rate_limit_exceeded_handler)
app.add_middleware(SlowAPIMiddleware)

# CORS - Allow all localhost ports in development
if is_production:
    cors_origins = [
        "https://lojiz.onrender.com",
        "https://lojiz.com",
        "https://www.lojiz.com",
    ]
    cors_origin_regex = None
else:
    # Development - allow any localhost port (Flutter uses random ports like 50421)
    cors_origins = [
        "http://localhost",
        "http://127.0.0.1",
    ]
    # Regex to match localhost with any port
    cors_origin_regex = r"^https?://(localhost|127\.0\.0\.1)(:\d+)?$"

app.add_middleware(
    CORSMiddleware,
    allow_origins=cors_origins,
    allow_origin_regex=cors_origin_regex,
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
    expose_headers=["*"],
    max_age=600,
)

# EXCEPTION HANDLERS
@app.exception_handler(RequestValidationError)
async def validation_exception_handler(request: Request, exc: RequestValidationError):
    logger.error(f"Validation error: {exc}")
    errors = []
    for error in exc.errors():
        field = ".".join(str(loc) for loc in error["loc"][1:])
        errors.append({"field": field, "message": error["msg"]})
    return JSONResponse(
        status_code=400,
        content={
            "success": False,
            "message": "Validation error. Please check your input.",
            "error_code": "VALIDATION_ERROR",
            "errors": errors,
        },
    )


@app.exception_handler(AuthException)
async def auth_exception_handler(request: Request, exc: AuthException): # type: ignore
    logger.warning(f"Auth error [{exc.error_code}]: {exc.message}")
    response = {"success": False, "message": exc.message, "error_code": exc.error_code}
    if exc.data:
        response["data"] = exc.data
    return JSONResponse(status_code=exc.status_code, content=response)


# ROUTERS
logger.info("=" * 70)
logger.info("Registering routers...")
logger.info("=" * 70)

# Authentication
app.include_router(auth.router, prefix="/api/auth", tags=["Authentication"])

# AI CHAT ROUTES (Two distinct systems)
# 1. AIDA Chat Screen - Full Agent (/ai/ask)
# 2. AIDA DM - Simple Agent (/ai/dm)

try:
    from app.ai.routes.chat import router as ai_chat_router
    from app.ai.routes.dm import router as ai_dm_router
    
    # Register main chat (/ai/ask)
    app.include_router(
        ai_chat_router,
        prefix="/ai",
        tags=["AIDA AI Chat (Full Agent / Chat Screen)"]
    )
    
    # Register DM agent (/ai/dm)
    app.include_router(
        ai_dm_router,
        prefix="/ai",
        tags=["AIDA AI DM (Conversational / Alert Agent)"]
    )
    
    logger.info("✅ AIDA AI Chat registered at /ai/ask (Graph-Based)")
    logger.info("✅ AIDA AI DM registered at /ai/dm (One-Shot)")
except ImportError as e:
    logger.error(f"❌ AI Chat/DM import error: {e}")

# AIDA Translation
try:
    from app.ai.routes.translate import router as translate_router
    app.include_router(translate_router, prefix="/api", tags=["AIDA Translate"])
    logger.info("✅ AIDA Translation registered at /api/translate")
except Exception as e:
    logger.error(f"❌ AIDA Translation import error: {e}")

# ============================================================
# LISTING ROUTERS
# ============================================================
from app.routes.listing import router as listing_router
from app.routes.media_upload import router as media_router
from app.routes.user_public import router as user_public_router
from app.routes.websocket_listings import router as ws_router
from app.routes.websocket_chat import router as ws_chat_router
from app.routes.reviews import router as reviews_router
from app.routes.search import router as search_router
from app.routes.conversations import router as conversations_router
from app.routes.wishlist import router as wishlist_router
from app.routes.booking import router as booking_router
from app.routes import stripe_connect

app.include_router(listing_router, prefix="/api/listings", tags=["Listings"])
app.include_router(media_router, tags=["Media Upload & Analysis"])
app.include_router(user_public_router, prefix="/api/users", tags=["Users"])
app.include_router(ws_router, tags=["WebSocket Listings"])
app.include_router(ws_chat_router, tags=["WebSocket Chat"])
app.include_router(reviews_router, prefix="/api/reviews", tags=["Reviews"])
app.include_router(search_router, prefix="/api/search", tags=["AIDA Search"])
app.include_router(conversations_router, prefix="/api/conversations", tags=["Conversations"])
app.include_router(wishlist_router, prefix="/api/wishlist", tags=["Wishlist"])
app.include_router(booking_router, prefix="/api/bookings", tags=["Bookings"])
app.include_router(stripe_connect.router, prefix="/api/stripe", tags=["Stripe Connect"])

# Contact Update (Email & Phone)
from app.routes.contact_update import router as contact_update_router
app.include_router(contact_update_router, prefix="/api/contact", tags=["Contact Update"])
logger.info("✅ Contact Update router registered at /api/contact")

from app.routes.quota import router as quota_router
app.include_router(quota_router, prefix="/api/quota", tags=["Quota"])
logger.info("✅ Quota router registered at /api/quota")

# Legal Pages (Privacy Policy & Terms of Service)
from app.routes.legal import router as legal_router
app.include_router(legal_router, prefix="/api/legal", tags=["Legal"])
logger.info("✅ Legal pages router registered at /api/legal")

# Notification Preferences + FCM
from app.routes.notifications import router as notifications_router
app.include_router(notifications_router, prefix="/api/notifications", tags=["Notifications"])
logger.info("✅ Notifications router registered at /api/notifications")

# Alerts Management (View, Delete, Pause/Resume)
from app.routes.alerts import router as alerts_router
app.include_router(alerts_router, prefix="/api", tags=["Alerts"])
logger.info("✅ Alerts router registered at /api/alerts")

logger.info("✅ Conversations router registered at /api/conversations")
logger.info("✅ Wishlist router registered at /api/wishlist")
logger.info("✅ Bookings router registered at /api/bookings")
logger.info("✅ Stripe Connect router registered at /api/stripe")

# ============================================================
# VOICE ROUTES (AIDA Voice Messaging)
# ============================================================
try:
    from app.routes.voice import router as voice_router
    from app.routes.voice_call_ws import router as voice_call_ws_router
    app.include_router(voice_router, prefix="/api", tags=["Voice"])
    app.include_router(voice_call_ws_router, tags=["Voice Call WebSocket"])
    logger.info("✅ Voice router registered at /api/voice")
    logger.info("✅ Voice Call WebSocket registered at /ws/voice-call")
except ImportError as e:
    logger.warning(f"⚠️ Voice router not available: {e}")

logger.info("=" * 70)
logger.info("✅ All routers registered successfully")
logger.info("=" * 70)


# ENDPOINTS

@app.get("/health", tags=["Health"])
async def health_check():
    """Health check endpoint"""
    try:
        redis_ok = False
        if redis_client:
            try:
                await redis_client.ping()
                redis_ok = True
            except Exception:
                redis_ok = False

        qdrant_ok = False
        if qdrant_client:
            try:
                await qdrant_client.get_collections()
                qdrant_ok = True
            except Exception:
                qdrant_ok = False

        try:
            ml = get_ml_extractor()
            ml_ok = ml is not None
        except Exception:
            ml_ok = False

        return {
            "status": "healthy",
            "service": "Lojiz Platform + Aida AI",
            "version": "2.0.0",
            "architecture": "Graph-Based (State Machine + Validation)",
            "environment": environment,
            "ai_endpoints": {
                "primary": "/ai/v1 (Graph-Based)",
                "fallback": "/ai/v2 (Legacy)",
            },
            "components": {
                "mongodb": "connected",
                "redis": "connected" if redis_ok else "disconnected",
                "qdrant": "connected" if qdrant_ok else "disconnected",
                "ml": "ready" if ml_ok else "not ready",
            }
        }
    except Exception as e:
        logger.error(f"Health check failed: {e}")
        return {
            "status": "unhealthy",
            "error": str(e),
        }


@app.get("/", tags=["Root"])
async def root():
    """Root endpoint - API information"""
    return {
        "name": "AIDA",
        "tagline": "Your AI Real Estate Agent",
        "platform": "Lojiz",
        "version": "2.0.0",
        "status": "operational",
        "environment": environment,
        "endpoints": {
            "chat": "/ai/ask",
            "dm": "/ai/dm",
            "health": "/health",
            "docs": "/docs",
        },
        "capabilities": [
            "Natural language property search",
            "Intelligent listing creation",
            "Real-time market insights",
            "Multi-language support (EN/FR)",
            "Voice message processing",
        ],
        "powered_by": "LangGraph State Machine",
        "made_with": "FastAPI + MongoDB + Redis",
    }


@app.options("/{full_path:path}", include_in_schema=False)
async def options_handler(full_path: str):
    """Handle CORS preflight requests"""
    return JSONResponse(status_code=200, content={})


# RUN
# To run this application:
# Development: uvicorn app.main:app --reload
# Production: gunicorn -w 4 -k uvicorn.workers.UvicornWorker app.main:app
# HF Spaces: python app.py