import os
from contextlib import asynccontextmanager

from fastapi import FastAPI, HTTPException
from fastapi.responses import HTMLResponse, FileResponse
from fastapi.staticfiles import StaticFiles
from fastapi.middleware.cors import CORSMiddleware
from fastapi.middleware.gzip import GZipMiddleware
from app.config import settings
from app.api.v1.audio import router as audio_router
from app.api.v1.tasks import router as tasks_router
from app.api.v1.multitrack import router as multitrack_router
from app.api.v1.auth import router as auth_router
from app.api.v1.admin import router as admin_router
from app.api.v1.projects import router as projects_router
from app.api.v1.user_config import router as user_config_router
from app.api.v1.ai_proxy import router as ai_proxy_router
from app.api.v1.ai_presets import router as ai_presets_router
from app.api.v1.plugins import router as plugins_router
from app.api.v1.media import router as media_router
from app.api.v1.system import router as system_router
from app.api.v1.presets import router as presets_router
from app.core.auth import seed_admin
from app.core.soundfont_scanner import SoundFontAutoScanner

# Ensure storage directories exist
os.makedirs(settings.UPLOADS_DIR, exist_ok=True)
os.makedirs(settings.PROCESSED_DIR, exist_ok=True)

_SF_SCANNER_STOP = None


@asynccontextmanager
async def lifespan(app: FastAPI):
    # Startup
    seed_admin()
    scanner = SoundFontAutoScanner()
    global _SF_SCANNER_STOP
    _SF_SCANNER_STOP = scanner.start_background(interval=30)
    yield
    # Shutdown
    if _SF_SCANNER_STOP is not None:
        _SF_SCANNER_STOP.set()


app = FastAPI(title="SonicForge API Engine", lifespan=lifespan)

app.add_middleware(GZipMiddleware, minimum_size=500)

# Auth is token/cookie based (no cookies required for CORS), so credentials are
# disabled — "*" + allow_credentials=True is rejected by browsers anyway.
app.add_middleware(
    CORSMiddleware,
    allow_origins=["*"],
    allow_credentials=False,
    allow_methods=["*"],
    allow_headers=["*"],
)

# Mount storage directory (must come before general /static mount)
# Bug #1: trước đây mount TOÀN BỘ STORAGE_DIR tại /static/audio — StaticFiles
# serve cả dotfile → attacker đọc .secret_key (forge admin token) + sonicforge.db
# (mọi user/hash) + temp/autosave.json mà KHÔNG cần auth. Fix: mount riêng từng
# thư mục con + chặn dotfile bằng SafeStaticFiles.
class SafeStaticFiles(StaticFiles):
    """StaticFiles chặn mọi đường dẫn chứa dotfile/.. (404 thay vì serve)."""

    async def get_response(self, path: str, scope):
        parts = path.split("/")
        if any(p.startswith(".") or p in ("", "..") for p in parts):
            raise HTTPException(status_code=404, detail="Not Found")
        return await super().get_response(path, scope)


app.mount("/static/audio/uploads", SafeStaticFiles(directory=settings.UPLOADS_DIR), name="uploads")
app.mount("/static/audio/processed", SafeStaticFiles(directory=settings.PROCESSED_DIR), name="processed")
# Mount app static files (js, css)
# Dùng settings.APP_DIR (freeze-aware) thay vì os.path.dirname(__file__):
# khi PyInstaller onefile, __file__ trỏ vào thư mục giải nén tạm _MEI...
# nhưng static/templates nằm trong sys._MEIPASS/app (config.py đã xử lý).
STATIC_DIR = os.path.join(settings.APP_DIR, "static")
# ⚠️ Fallback an toàn: nếu vì lý do nào đó static không nằm đúng chỗ (vd
# bundle thiếu file, chạy từ nơi khác), thử các vị trí khác; nếu vẫn không
# có → TỰ TẠO thư mục rỗng để app KHÔNG crash khi khởi động (lỗi "Directory
# does not exist" từ StaticFiles làm engine chết ngay lúc import — đã gặp).
if not os.path.isdir(STATIC_DIR):
    for cand in [
        os.path.join(getattr(sys, "_MEIPASS", ""), "app", "static"),
        os.path.join(os.path.dirname(os.path.abspath(__file__)), "static"),
    ]:
        if cand and os.path.isdir(cand):
            STATIC_DIR = cand
            break
    else:
        try:
            os.makedirs(STATIC_DIR, exist_ok=True)
        except OSError:
            pass
app.mount("/static", StaticFiles(directory=STATIC_DIR), name="static")

# Include routers
app.include_router(audio_router, prefix="/api/v1/audio", tags=["audio"])
app.include_router(tasks_router, prefix="/api/v1/audio", tags=["tasks"])
app.include_router(multitrack_router, prefix="/api/v1/multitrack", tags=["multitrack"])
app.include_router(auth_router, prefix="/api/v1/auth", tags=["auth"])
app.include_router(admin_router, prefix="/api/v1/admin", tags=["admin"])
app.include_router(projects_router, prefix="/api/v1/projects", tags=["projects"])
app.include_router(user_config_router, prefix="/api/v1/user", tags=["user_config"])
app.include_router(ai_proxy_router, prefix="/api/v1/ai", tags=["ai"])
app.include_router(ai_presets_router, prefix="/api/v1/ai", tags=["ai"])
app.include_router(plugins_router, prefix="/api/v1/plugins", tags=["plugins"])
app.include_router(media_router, prefix="/api/v1/media", tags=["media"])
app.include_router(system_router, prefix="/api/v1/system", tags=["system"])
app.include_router(presets_router, prefix="/api/v1/presets", tags=["presets"])


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

@app.get("/", response_class=HTMLResponse)
async def get_index():
    index_path = os.path.join(settings.TEMPLATES_DIR, "index.html")
    if not os.path.exists(index_path):
        return HTMLResponse(content=f"<h1>SonicForge Studio: index.html not found at {index_path}</h1>", status_code=404)
    with open(index_path, "r", encoding="utf-8") as file:
        resp = HTMLResponse(content=file.read(), status_code=200)
        # no-cache: index.html PHẢI luôn mới (các bundle JS dùng ?v= để bust) —
        # nếu browser cache HTML cũ → stamp cũ → tải bundle cũ (bug "không load
        # được bundle mới" ở incognito — cache heuristic không có Cache-Control).
        resp.headers["Cache-Control"] = "no-cache, no-store, must-revalidate"
        return resp


@app.get("/favicon.svg")
async def get_favicon():
    favicon_path = os.path.join(settings.TEMPLATES_DIR, "favicon.svg")
    if os.path.exists(favicon_path):
        return FileResponse(favicon_path, media_type="image/svg+xml")
    return HTMLResponse(content="", status_code=404)


@app.get("/ai-prompt-generator", response_class=HTMLResponse)
async def get_ai_prompt_generator():
    md_path = os.path.join(settings.BASE_DIR, "md", "49_AI_PROMPT_GENERATOR.md")
    if not os.path.exists(md_path):
        return HTMLResponse(content="<h1>File not found</h1>", status_code=404)
    with open(md_path, "r", encoding="utf-8") as file:
        return HTMLResponse(content=file.read(), status_code=200)
