"""
INDELIS API — Cemetery & Gravesite Management SaaS Platform
"""
import logging
from contextlib import asynccontextmanager

from typing import Optional

from fastapi import FastAPI, HTTPException, Request
from fastapi.exceptions import RequestValidationError
from fastapi.middleware.cors import CORSMiddleware
from starlette.exceptions import HTTPException as StarletteHTTPException

from src.database.load_models import load_all_models

load_all_models()

from src.core.config import settings
from src.core.exceptions import AppException
from src.core.handlers.errors import (
    app_exception_handler,
    http_exception_handler,
    validation_error_handler,
    general_exception_handler,
)
from src.middleware.logging import LoggingMiddleware
from src.middleware.security import SecurityHeadersMiddleware
from src.middleware.tenant import TenantMiddleware
from src.database.session import AsyncSessionLocal

logging.basicConfig(
    level=getattr(logging, settings.LOG_LEVEL, logging.INFO),
    format="%(asctime)s %(name)s %(levelname)s %(message)s",
)
logger = logging.getLogger(__name__)


@asynccontextmanager
async def lifespan(app: FastAPI):
    logger.info(f"INDELIS API starting — env={settings.ENVIRONMENT}")
    # Expose session factory on app state for middleware
    app.state.db_session = AsyncSessionLocal
    yield
    logger.info("INDELIS API shutting down")


app = FastAPI(
    title="INDELIS API",
    description="Cemetery & Gravesite Management SaaS Platform API",
    version="1.0.0",
    debug=settings.DEBUG,
    # A05/API9: never expose interactive docs or the OpenAPI schema in production.
    docs_url=None if settings.is_production else "/api/docs",
    redoc_url=None if settings.is_production else "/api/redoc",
    openapi_url=None if settings.is_production else "/api/openapi.json",
    lifespan=lifespan,
)

# ── CORS ──────────────────────────────────────────────────────────────────────
app.add_middleware(
    CORSMiddleware,
    allow_origins=settings.ALLOWED_ORIGINS,
    # Only allow HTTPS for indelis.com subdomains; localhost dev origins use
    # the explicit allow_origins list above, so http:// is not needed here.
    allow_origin_regex=r"https://[a-zA-Z0-9-]+\.indelis\.com|https?://[^/]+\.localhost(:\d+)?",
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)

# ── Custom Middleware (last-added runs first) ──────────────────────────────────
app.add_middleware(TenantMiddleware)
app.add_middleware(SecurityHeadersMiddleware)
app.add_middleware(LoggingMiddleware)

# ── Exception Handlers ────────────────────────────────────────────────────────
app.add_exception_handler(AppException, app_exception_handler)
app.add_exception_handler(StarletteHTTPException, http_exception_handler)
app.add_exception_handler(RequestValidationError, validation_error_handler)
app.add_exception_handler(Exception, general_exception_handler)


# ── Health Checks ─────────────────────────────────────────────────────────────
@app.get("/health", tags=["Health"])
async def health():
    return {"status": "healthy", "service": "indelis-api", "version": "1.0.0"}


@app.get("/health/live", tags=["Health"])
async def liveness():
    return {"status": "alive"}


@app.get("/health/ready", tags=["Health"])
async def readiness():
    from src.database.session import engine
    from sqlalchemy import text
    try:
        async with engine.connect() as conn:
            await conn.execute(text("SELECT 1"))
        db_ok = True
    except Exception:
        db_ok = False

    try:
        from src.database.session import get_redis
        redis = await get_redis()
        await redis.ping()
        redis_ok = True
    except Exception:
        redis_ok = False

    ready = db_ok and redis_ok
    return {
        "status": "ready" if ready else "degraded",
        "database": "ok" if db_ok else "error",
        "redis": "ok" if redis_ok else "error",
    }


# ── Routers ───────────────────────────────────────────────────────────────────
from fastapi import Depends
from src.core.dependencies import require_onboarding_complete
from src.database.session import get_db

from src.apps.auth.router import router as auth_router
from src.apps.tenants.router import router as tenants_router
from src.apps.users.router import router as users_router
from src.apps.sections.router import router as sections_router
from src.apps.plots.router import router as plots_router
from src.apps.plots.routers.plot_status_router import router as plot_statuses_router
from src.apps.plots.routers.plot_group_router import router as plot_groups_router
from src.apps.records.router import router as records_router
from src.apps.sales.router import router as sales_router
from src.apps.billing.router import router as billing_router
from src.apps.scheduling.router import router as scheduling_router
from src.apps.scheduling.scheduling_router import router as scheduling_v2_router
from src.apps.memorials.router import router as memorials_router
from src.apps.news.router import router as news_router
from src.apps.crew_members.router import router as crew_members_router
from src.apps.reports.router import router as reports_router
from src.apps.settings.router import router as settings_router
from src.apps.pages.router import router as pages_router
from src.apps.public.router import router as public_router
from src.apps.site_admin.router import router as site_admin_router
from src.apps.ai.router import router as ai_router
from src.apps.documents.router import router as documents_router
from src.apps.dashboard.router import router as dashboard_router
from src.apps.onboarding.router import router as onboarding_router
from src.apps.cemetery_map.router import (
    accounts_router as cemetery_accounts_router,
    cemetery_map_router,
)

API_V1 = "/api/v1"
# Gate applied to all tenant-scoped routers except auth and onboarding
_onboarding_gate = [Depends(require_onboarding_complete)]

app.include_router(auth_router, prefix=API_V1)
app.include_router(onboarding_router, prefix=API_V1)
app.include_router(tenants_router, prefix=API_V1)
app.include_router(users_router, prefix=API_V1, dependencies=_onboarding_gate)
app.include_router(sections_router, prefix=API_V1, dependencies=_onboarding_gate)
app.include_router(plots_router, prefix=API_V1, dependencies=_onboarding_gate)
app.include_router(plot_statuses_router, prefix=API_V1, dependencies=_onboarding_gate)
app.include_router(plot_groups_router, prefix=API_V1, dependencies=_onboarding_gate)
app.include_router(records_router, prefix=API_V1, dependencies=_onboarding_gate)
app.include_router(sales_router, prefix=API_V1, dependencies=_onboarding_gate)
app.include_router(billing_router, prefix=API_V1, dependencies=_onboarding_gate)
app.include_router(scheduling_router, prefix=API_V1, dependencies=_onboarding_gate)
app.include_router(scheduling_v2_router, prefix=API_V1, dependencies=_onboarding_gate)
app.include_router(memorials_router, prefix=API_V1, dependencies=_onboarding_gate)
app.include_router(news_router, prefix=API_V1, dependencies=_onboarding_gate)
app.include_router(crew_members_router, prefix=API_V1, dependencies=_onboarding_gate)
app.include_router(reports_router, prefix=API_V1, dependencies=_onboarding_gate)
app.include_router(settings_router, prefix=API_V1, dependencies=_onboarding_gate)
app.include_router(pages_router, prefix=API_V1, dependencies=_onboarding_gate)
app.include_router(ai_router, prefix=API_V1, dependencies=_onboarding_gate)
app.include_router(documents_router, prefix=API_V1, dependencies=_onboarding_gate)
app.include_router(dashboard_router, prefix=API_V1, dependencies=_onboarding_gate)
app.include_router(cemetery_accounts_router, prefix=API_V1, dependencies=_onboarding_gate)
app.include_router(cemetery_map_router, prefix=API_V1, dependencies=_onboarding_gate)
app.include_router(public_router, prefix="/api")
app.include_router(site_admin_router, prefix="/api")

# ── Stripe Webhook & WebSocket ────────────────────────────────────────────────
from fastapi import WebSocket, WebSocketDisconnect, Query
from sqlalchemy.ext.asyncio import AsyncSession
from src.core.websocket import manager
from src.core.security import decode_access_token


@app.post("/api/webhooks/stripe", include_in_schema=False)
async def stripe_webhook(request: Request, db: AsyncSession = Depends(get_db)):
    """Stripe webhook handler — verifies signature, processes events, broadcasts via WS."""
    import stripe as _stripe
    from src.apps.payments.services.stripe_service import construct_event
    from src.apps.payments.services.payment_service import (
        handle_payment_succeeded,
        handle_payment_failed,
    )

    payload = await request.body()
    sig_header = request.headers.get("stripe-signature", "")
    try:
        event = construct_event(payload, sig_header)
    except (_stripe.error.SignatureVerificationError, ValueError):
        raise HTTPException(status_code=400, detail="Invalid Stripe signature")

    event_type = event["type"]
    intent = event["data"]["object"]
    intent_id = intent.get("id", "")
    charge_id: Optional[str] = None
    charges = intent.get("charges", {}).get("data", [])
    if charges:
        charge_id = charges[0].get("id")
    metadata = intent.get("metadata", {})
    contract_id = metadata.get("contract_id", "")
    tenant_id = metadata.get("tenant_id", "")
    receipt_email = intent.get("receipt_email") or metadata.get("receipt_email")

    if not contract_id or not tenant_id:
        return {"received": True}

    if event_type == "payment_intent.succeeded":
        amount_received = intent.get("amount_received", 0)
        currency = intent.get("currency", "cad")
        processed = await handle_payment_succeeded(
            db=db,
            stripe_payment_intent_id=intent_id,
            stripe_charge_id=charge_id,
            amount_received_cents=amount_received,
            currency=currency,
            contract_id=contract_id,
            tenant_id=tenant_id,
            receipt_email=receipt_email,
        )
        if processed:
            # Load family name + contract reference for WebSocket payload
            from sqlalchemy import select as _select
            from src.apps.sales.models.contract import Contract as _Contract
            import uuid as _uuid
            try:
                _contract = (await db.execute(
                    _select(_Contract).where(_Contract.id == _uuid.UUID(contract_id))
                )).scalar_one_or_none()
                family_name = _contract.purchaser_name or "" if _contract else ""
                opp_id = str(_contract.opportunity_id) if _contract and _contract.opportunity_id else ""
            except Exception:
                family_name = ""
                opp_id = ""

            from datetime import datetime, timezone as _tz
            await manager.broadcast_to_tenant(tenant_id, {
                "type": "payment_received",
                "opportunityId": opp_id,
                "contractId": contract_id,
                "amount": float(amount_received) / 100,
                "currency": currency.upper(),
                "paymentDate": datetime.now(_tz.utc).isoformat(),
                "familyName": family_name,
            })

    elif event_type == "payment_intent.payment_failed":
        amount = intent.get("amount", 0)
        currency = intent.get("currency", "cad")
        await handle_payment_failed(
            db=db,
            stripe_payment_intent_id=intent_id,
            stripe_charge_id=charge_id,
            amount_cents=amount,
            currency=currency,
            contract_id=contract_id,
            tenant_id=tenant_id,
        )

    return {"received": True}


# ── WebSocket — Admin real-time notifications ──────────────────────────────────
@app.websocket("/ws/admin")
async def websocket_admin(
    websocket: WebSocket,
    token: str = Query(...),
):
    """Authenticated WebSocket for admin portal real-time events.

    Validates the JWT on connect; closes with code 4401 on invalid/expired token.
    Tenant is extracted from the token payload.
    """
    payload = decode_access_token(token)
    if payload is None:
        await websocket.close(code=4401)
        return

    tenant_id = payload.get("tenant_id", "")
    if not tenant_id:
        await websocket.close(code=4401)
        return

    await manager.connect(websocket, str(tenant_id))
    try:
        while True:
            await websocket.receive_text()  # keepalive ping/pong
    except WebSocketDisconnect:
        manager.disconnect(websocket, str(tenant_id))

# ── Local file uploads (dev fallback when S3 bucket doesn't exist) ────────────
import os
from fastapi.staticfiles import StaticFiles
_uploads_dir = os.path.join(os.path.dirname(__file__), "..", "uploads")
os.makedirs(_uploads_dir, exist_ok=True)
app.mount("/uploads", StaticFiles(directory=_uploads_dir), name="uploads")


if __name__ == "__main__":
    import uvicorn

    uvicorn.run(
        "src.main:app",
        host="0.0.0.0",
        port=8000,
        reload=settings.DEBUG,
        log_level=settings.LOG_LEVEL.lower(),
    )
