import asyncio
import logging
from contextlib import asynccontextmanager
from pathlib import Path

from fastapi import Depends, FastAPI
from fastapi.middleware.cors import CORSMiddleware
from fastapi.staticfiles import StaticFiles
from sqlalchemy.orm import Session

from app.config import get_settings
from app.database import get_db
from app.schemas import (
    ForgotPasswordRequest,
    Message,
    ResetPasswordRequest,
    UserTokenResponse,
    VerifyEmailRequest,
    VerifyOTPRequest,
)
from app.database import (
    SessionLocal,
    engine,
    init_db,
    sync_user_profile_columns,
    sync_user_track_traces_table,
    sync_users_list_indexes,
    sync_users_email_verified_column,
)
from app.programme.services.programme_service import sync_programme_string_columns
from app.routers.admin import router as admin_router
from app.routers.contact import router as contact_router
from app.routers.user_panel import router as user_router
from app.services.admin_profile_service import backfill_admin_profiles, sync_admin_table
from app.services.admin_service import sync_admin_schemas


logger = logging.getLogger(__name__)


class _IgnoredAccessPathFilter(logging.Filter):
    ignored_paths = (
        "/api/v1/user/member-notifications-unread-count",
        "/health",
    )

    def filter(self, record: logging.LogRecord) -> bool:
        message = record.getMessage()
        return not any(path in message for path in self.ignored_paths)


def _install_access_log_filter() -> None:
    access_logger = logging.getLogger("uvicorn.access")
    if not any(isinstance(item, _IgnoredAccessPathFilter) for item in access_logger.filters):
        access_logger.addFilter(_IgnoredAccessPathFilter())


_install_access_log_filter()


def _run_startup_sync() -> None:
    """Blocking DB bootstrap — run off the event loop so reload/shutdown stay clean."""
    # Migrate enum columns before ORM reads (fixes LookupError on custom module kinds).
    sync_programme_string_columns(engine)
    init_db()
    sync_user_track_traces_table(engine)
    sync_users_email_verified_column(engine)
    sync_user_profile_columns(engine)
    sync_users_list_indexes(engine)
    sync_admin_table(engine)
    sync_admin_schemas(engine)
    db = SessionLocal()
    try:
        backfill_admin_profiles(db)
    finally:
        db.close()


@asynccontextmanager
async def lifespan(app: FastAPI):
    try:
        await asyncio.to_thread(_run_startup_sync)
    except asyncio.CancelledError:
        logger.info("Startup cancelled (server reload or shutdown)")
        raise
    try:
        yield
    finally:
        # Allow in-flight tasks from reload to finish without generator errors.
        await asyncio.sleep(0)
        engine.dispose()


settings = get_settings()
app = FastAPI(title="Admin & User API", lifespan=lifespan)

app.add_middleware(
    CORSMiddleware,
    allow_origins=["*"],
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)

app.include_router(admin_router, prefix=settings.http_api_prefix)
app.include_router(user_router, prefix=settings.http_api_prefix)
app.include_router(contact_router, prefix=settings.http_api_prefix)

_upload_root = Path(settings.community_upload_dir)
if not _upload_root.is_absolute():
    _upload_root = Path(__file__).resolve().parent.parent / _upload_root
_upload_root.mkdir(parents=True, exist_ok=True)
app.mount(
    f"{settings.http_api_prefix}/{settings.community_uploads_url_segment.strip('/')}",
    StaticFiles(directory=str(_upload_root)),
    name="uploads",
)


@app.get(settings.health_url_path, include_in_schema=False)
def health():
    return {"status": "ok"}


# Frontend compatibility: some clients call /v1/... or /api/auth/... without the full prefix.
from app.routers.admin.auth import (  # noqa: E402
    admin_forgot_password_handler,
    admin_reset_password,
    admin_verify_otp,
)
from app.routers.user_panel.auth import (  # noqa: E402
    _user_forgot_password_handler,
    _user_verification_otp_handler,
    user_reset_password,
    user_verify_email,
    user_verify_otp,
)
from app.services.password_reset_service import find_user_by_email  # noqa: E402
from app.models import UserRole  # noqa: E402


@app.post("/v1/admin/auth/forgot-password", response_model=Message, include_in_schema=False)
async def admin_forgot_password_alias_v1(
    body: ForgotPasswordRequest,
    db: Session = Depends(get_db),
):
    return await admin_forgot_password_handler(body, db)


@app.post("/v1/admin/auth/reset-password", response_model=Message, include_in_schema=False)
def admin_reset_password_alias_v1(
    body: ResetPasswordRequest,
    db: Session = Depends(get_db),
):
    return admin_reset_password(body, db)


@app.post("/v1/admin/auth/verify-otp", response_model=Message, include_in_schema=False)
def admin_verify_otp_alias_v1(
    body: VerifyOTPRequest,
    db: Session = Depends(get_db),
):
    return admin_verify_otp(body, db)


@app.post("/v1/user/auth/forgot-password", response_model=Message, include_in_schema=False)
async def user_forgot_password_alias_v1(
    body: ForgotPasswordRequest,
    db: Session = Depends(get_db),
):
    return await _user_forgot_password_handler(body, db)


@app.post("/v1/user/auth/send-otp", response_model=Message, include_in_schema=False)
async def user_send_otp_alias_v1(
    body: ForgotPasswordRequest,
    db: Session = Depends(get_db),
):
    return await _user_verification_otp_handler(body, db)


@app.post("/v1/user/auth/verify-email", response_model=UserTokenResponse, include_in_schema=False)
def user_verify_email_alias_v1(
    body: VerifyEmailRequest,
    db: Session = Depends(get_db),
):
    return user_verify_email(body, db)


@app.post("/v1/user/auth/verify-otp", response_model=Message, include_in_schema=False)
def user_verify_otp_alias_v1(
    body: VerifyOTPRequest,
    db: Session = Depends(get_db),
):
    return user_verify_otp(body, db)


@app.post("/api/auth/forgot-password", response_model=Message, include_in_schema=False)
async def auth_forgot_password_alias(
    body: ForgotPasswordRequest,
    db: Session = Depends(get_db),
):
    email = str(body.email)
    if find_user_by_email(db, email, role=UserRole.admin):
        return await admin_forgot_password_handler(body, db)
    return await _user_forgot_password_handler(body, db)


@app.post("/api/v1/auth/forgot-password", response_model=Message, include_in_schema=False)
async def auth_forgot_password_alias_api_v1(
    body: ForgotPasswordRequest,
    db: Session = Depends(get_db),
):
    return await auth_forgot_password_alias(body, db)


@app.post("/api/auth/send-otp", response_model=Message, include_in_schema=False)
async def auth_send_otp_alias(
    body: ForgotPasswordRequest,
    db: Session = Depends(get_db),
):
    email = str(body.email)
    if find_user_by_email(db, email, role=UserRole.admin):
        return await admin_forgot_password_handler(body, db)
    return await _user_verification_otp_handler(body, db)


@app.post("/api/v1/auth/send-otp", response_model=Message, include_in_schema=False)
async def auth_send_otp_alias_api_v1(
    body: ForgotPasswordRequest,
    db: Session = Depends(get_db),
):
    return await auth_send_otp_alias(body, db)


@app.post("/api/auth/verify-email", response_model=UserTokenResponse, include_in_schema=False)
def auth_verify_email_alias(
    body: VerifyEmailRequest,
    db: Session = Depends(get_db),
):
    return user_verify_email(body, db)


@app.post("/api/v1/auth/verify-email", response_model=UserTokenResponse, include_in_schema=False)
def auth_verify_email_alias_api_v1(
    body: VerifyEmailRequest,
    db: Session = Depends(get_db),
):
    return auth_verify_email_alias(body, db)


@app.post("/api/auth/verify-otp", response_model=Message, include_in_schema=False)
def auth_verify_otp_alias(
    body: VerifyOTPRequest,
    db: Session = Depends(get_db),
):
    email = str(body.email)
    if find_user_by_email(db, email, role=UserRole.admin):
        return admin_verify_otp(body, db)
    return user_verify_otp(body, db)


@app.post("/api/v1/auth/verify-otp", response_model=Message, include_in_schema=False)
def auth_verify_otp_alias_api_v1(
    body: VerifyOTPRequest,
    db: Session = Depends(get_db),
):
    return auth_verify_otp_alias(body, db)
