from contextlib import asynccontextmanager
from datetime import datetime
import logging
import logging.config
from time import perf_counter
from zoneinfo import ZoneInfo

from fastapi import FastAPI, Request
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import FileResponse
from fastapi.staticfiles import StaticFiles

from app.config import settings
from app.core.commercial import CommercialFeatureError, commercial_feature_exception_handler
from app.redis_client import close_redis, ping_redis
from app.routers.api_v1 import api_v1_router
from app.routers.zjmf_downstream import router as zjmf_downstream_router
from app.services.agent_settlement_scheduler import (
    shutdown_agent_settlement_scheduler,
    start_agent_settlement_scheduler,
)
from app.services.agent_lifecycle_scheduler import (
    shutdown_agent_lifecycle_scheduler,
    start_agent_lifecycle_scheduler,
)
from app.services.cloud_server_scheduler import shutdown_scheduler, start_scheduler
from app.services.instance_lifecycle_scheduler import (
    shutdown_instance_lifecycle_scheduler,
    start_instance_lifecycle_scheduler,
)
from app.services.instance_real_data_scheduler import shutdown_instance_real_data_scheduler, start_instance_real_data_scheduler
from app.services.promotion_coupon_scheduler import (
    shutdown_promotion_coupon_scheduler,
    start_promotion_coupon_scheduler,
)
from app.services.recharge_scheduler import shutdown_recharge_scheduler, start_recharge_scheduler
from app.services.upstream_action_scheduler import (
    shutdown_upstream_action_scheduler,
    start_upstream_action_scheduler,
)
from app.services.verification_cleanup_scheduler import (
    shutdown_verification_cleanup_scheduler,
    start_verification_cleanup_scheduler,
)
from app.services.zjmf_stock_sync_scheduler import (
    shutdown_zjmf_stock_sync_scheduler,
    start_zjmf_stock_sync_scheduler,
)


class CachedStaticFiles(StaticFiles):
    """StaticFiles with Cache-Control headers for immutable assets like images."""

    async def get_response(self, path: str, scope: tuple) -> FileResponse:
        response = await super().get_response(path, scope)
        ext = path.rsplit(".", 1)[-1].lower() if "." in path else ""
        if ext in ("png", "jpg", "jpeg", "gif", "webp", "svg", "ico"):
            # Immutable image files — cache aggressively (1 year)
            response.headers["Cache-Control"] = "public, max-age=31536000, immutable"
        elif ext in ("css", "js", "woff", "woff2"):
            response.headers["Cache-Control"] = "public, max-age=2592000"
        return response

_LOG_LEVEL = "DEBUG" if settings.DEBUG else "INFO"
_ACCESS_LOG_TZ = ZoneInfo("Asia/Shanghai")

logging.config.dictConfig({
    "version": 1,
    "disable_existing_loggers": False,
    "incremental": False,
    "formatters": {
        "default": {
            "format": "%(asctime)s [%(levelname)s] %(name)s: %(message)s",
            "datefmt": "%Y-%m-%d %H:%M:%S",
        },
        "access": {
            "format": "%(message)s",
        },
    },
    "handlers": {
        "console": {
            "class": "logging.StreamHandler",
            "formatter": "default",
        },
        "access_console": {
            "class": "logging.StreamHandler",
            "formatter": "access",
        }
    },
    "loggers": {
        "qycw": {"level": _LOG_LEVEL, "handlers": ["console"], "propagate": False},
        "qycw.access": {"level": "INFO", "handlers": ["access_console"], "propagate": False},
        "uvicorn": {"level": "WARNING", "handlers": ["console"], "propagate": False},
        "uvicorn.error": {"level": "WARNING", "handlers": ["console"], "propagate": False},
        "uvicorn.access": {"level": "WARNING", "handlers": ["console"], "propagate": False},
        "sqlalchemy.engine": {
            "level": "WARNING",
            "handlers": ["console"],
            "propagate": False,
        },
    },
    "root": {"level": "WARNING", "handlers": ["console"]},
})

# Force-set SQLAlchemy engine level to suppress query logs
logging.getLogger("sqlalchemy.engine").setLevel(logging.WARNING)

log = logging.getLogger("qycw")
access_log = logging.getLogger("qycw.access")


def _should_access_log(request: Request) -> bool:
    path = request.url.path
    return path == "/health" or path.startswith("/api")


@asynccontextmanager
async def lifespan(app: FastAPI):
    try:
        await ping_redis()
    except Exception as e:
        log.warning("Redis ping failed at startup: %s", e)
    try:
        start_scheduler()
    except Exception:
        log.exception("cloud_server scheduler failed to start")
    try:
        start_recharge_scheduler()
    except Exception:
        log.exception("recharge scheduler failed to start")
    try:
        start_instance_real_data_scheduler()
    except Exception:
        log.exception("instance real_data scheduler failed to start")
    try:
        start_instance_lifecycle_scheduler()
    except Exception:
        log.exception("instance lifecycle scheduler failed to start")
    try:
        start_upstream_action_scheduler()
    except Exception:
        log.exception("upstream action scheduler failed to start")
    try:
        start_zjmf_stock_sync_scheduler()
    except Exception:
        log.exception("zjmf stock sync scheduler failed to start")
    try:
        start_agent_settlement_scheduler()
    except Exception:
        log.exception("agent settlement scheduler failed to start")
    try:
        start_agent_lifecycle_scheduler()
    except Exception:
        log.exception("agent lifecycle scheduler failed to start")
    try:
        start_promotion_coupon_scheduler()
    except Exception:
        log.exception("promotion coupon scheduler failed to start")
    try:
        start_verification_cleanup_scheduler()
    except Exception:
        log.exception("verification cleanup scheduler failed to start")
    try:
        yield
    finally:
        shutdown_scheduler()
        shutdown_recharge_scheduler()
        shutdown_instance_real_data_scheduler()
        shutdown_instance_lifecycle_scheduler()
        shutdown_upstream_action_scheduler()
        shutdown_zjmf_stock_sync_scheduler()
        shutdown_agent_settlement_scheduler()
        shutdown_agent_lifecycle_scheduler()
        shutdown_promotion_coupon_scheduler()
        shutdown_verification_cleanup_scheduler()
        await close_redis()


app = FastAPI(title=settings.APP_NAME, version=settings.APP_VERSION, lifespan=lifespan)
settings.MEDIA_ROOT_PATH.mkdir(parents=True, exist_ok=True)

app.add_exception_handler(CommercialFeatureError, commercial_feature_exception_handler)

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


@app.middleware("http")
async def concise_access_log(request: Request, call_next):
    started = perf_counter()
    status_code = 500
    try:
        response = await call_next(request)
        status_code = response.status_code
        return response
    finally:
        if _should_access_log(request):
            elapsed_ms = int((perf_counter() - started) * 1000)
            timestamp = datetime.now(_ACCESS_LOG_TZ).strftime("%Y-%m-%d %H:%M:%S")
            access_log.info(
                "%s %s %s %s %sms",
                timestamp,
                request.method,
                request.url.path,
                status_code,
                elapsed_ms,
            )

app.include_router(api_v1_router)
app.include_router(zjmf_downstream_router, prefix="/api", tags=["zjmf-downstream"])
app.mount(settings.MEDIA_URL, CachedStaticFiles(directory=str(settings.MEDIA_ROOT_PATH)), name="media")


@app.get("/health")
async def health():
    return {"status": "ok", "version": settings.APP_VERSION}
