from fastapi import FastAPI, Depends
from fastapi.middleware.cors import CORSMiddleware
from fastapi.middleware.gzip import GZipMiddleware
from fastapi.responses import JSONResponse
from fastapi.websockets import WebSocket, WebSocketDisconnect
from fastapi.staticfiles import StaticFiles
from slowapi import Limiter, _rate_limit_exceeded_handler
from slowapi.util import get_remote_address
from slowapi.errors import RateLimitExceeded
from starlette.middleware.base import BaseHTTPMiddleware
from app.config import settings
from app.api import auth_router, streams_router, comments_router, tags_router, health_router, chat_router, users_router, admin_router, twitch_auth_router, backup_router, settings_router
from app.api.roles import router as roles_router
from app.api.route_info import router as route_info_router, set_app
from app.websocket import manager
from app.sse import sse_endpoint
from app.database import engine, Base
from app.models import *
from app.auth import get_current_user, get_current_admin_user
import logging
import os
from uuid import UUID
import threading
from pathlib import Path

# Configure logging
logger = logging.getLogger(__name__)
logger.setLevel(logging.INFO)

# Add console handler to ensure logs are output
handler = logging.StreamHandler()
handler.setLevel(logging.INFO)
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
handler.setFormatter(formatter)
logger.addHandler(handler)

# Enable uvicorn access logs for debugging
logging.getLogger("uvicorn.access").setLevel(logging.INFO)

# Create database tables
Base.metadata.create_all(bind=engine)

# Ensure at least one admin exists
def ensure_admin_exists():
    from app.database import SessionLocal
    from app.models.user import User
    from app.models.permission import Role

    db = SessionLocal()
    try:
        # Check if admin role exists
        admin_role = db.query(Role).filter(Role.name == "admin").first()
        if not admin_role:
            # Create admin role
            admin_role = Role(name="admin", description="Administrator with full access", is_default=True, priority=100)
            db.add(admin_role)
            db.commit()
            db.refresh(admin_role)
            logger.info("Created admin role")

        # Check if any admin exists
        admin_exists = db.query(User).filter(User.role_id == admin_role.id).first()

        if not admin_exists:
            # No admin exists, make the oldest user admin
            oldest_user = db.query(User).order_by(User.created_at.asc()).first()
            if oldest_user:
                oldest_user.role_id = admin_role.id
                db.commit()
                logger.info(f"Made user {oldest_user.username} an admin (oldest account)")
            else:
                logger.warning("No users exist yet - first registered user will be admin")
    finally:
        db.close()

ensure_admin_exists()

# Import restore status from backup module
from app.api.backup import restore_status, restore_lock

# Maintenance mode middleware
class MaintenanceModeMiddleware(BaseHTTPMiddleware):
    async def dispatch(self, request, call_next):
        # Check if restore is in progress
        with restore_lock:
            is_restoring = restore_status.get("is_restoring", False)

        # Allow restore status endpoint and health endpoint during maintenance
        if is_restoring:
            path = request.url.path
            if path in ["/api/admin/backup/restore/status", "/health", "/api/health"]:
                return await call_next(request)

            # Return maintenance mode response
            return JSONResponse(
                status_code=503,
                content={
                    "detail": "System is in maintenance mode during backup restore. Please try again later.",
                    "is_restoring": True,
                    "restore_status": restore_status
                }
            )

        return await call_next(request)

# Security headers middleware
class SecurityHeadersMiddleware(BaseHTTPMiddleware):
    async def dispatch(self, request, call_next):
        response = await call_next(request)
        response.headers["X-Content-Type-Options"] = "nosniff"
        response.headers["X-Frame-Options"] = "DENY"
        response.headers["X-XSS-Protection"] = "1; mode=block"
        response.headers["Referrer-Policy"] = "strict-origin-when-cross-origin"
        response.headers["Permissions-Policy"] = "geolocation=(), microphone=(), camera=()"
        
        # Content Security Policy (CSP)
        # Allow scripts and styles from same origin and inline styles for development
        csp = (
            "default-src 'self'; "
            "script-src 'self' 'unsafe-inline' 'unsafe-eval'; "
            "style-src 'self' 'unsafe-inline'; "
            "img-src 'self' data: https:; "
            "font-src 'self' data:; "
            "connect-src 'self' ws://localhost:* wss://localhost:*; "
            "media-src 'self' blob: data:; "
            "object-src 'none'; "
            "base-uri 'self'; "
            "form-action 'self';"
        )
        response.headers["Content-Security-Policy"] = csp
        
        # Strict-Transport-Security (HSTS)
        # Only enable in production with HTTPS
        if settings.ENVIRONMENT == "production":
            response.headers["Strict-Transport-Security"] = "max-age=31536000; includeSubDomains; preload"
        
        return response

# Initialize rate limiter
limiter = Limiter(key_func=get_remote_address)

# Create FastAPI app
app = FastAPI(
    title="StreamA2026 API",
    description="Streaming platform API with HLS/DASH support",
    version="1.0.0"
)

# Add rate limiter to app state
app.state.limiter = limiter
app.add_exception_handler(RateLimitExceeded, _rate_limit_exceeded_handler)

# Maintenance mode middleware (must be added FIRST to be the outermost middleware)
app.add_middleware(MaintenanceModeMiddleware)

# Security headers middleware
app.add_middleware(SecurityHeadersMiddleware)

# GZip compression middleware
app.add_middleware(GZipMiddleware, minimum_size=1000)

# CORS middleware
app.add_middleware(
    CORSMiddleware,
    allow_origins=settings.allowed_origins_list,
    allow_credentials=True,
    allow_methods=["GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS"],
    allow_headers=["Authorization", "Content-Type", "Accept"],
    expose_headers=["Content-Type", "Content-Length"],
)

# Include routers
app.include_router(auth_router)
app.include_router(streams_router)
app.include_router(comments_router)
app.include_router(tags_router)
app.include_router(health_router)
app.include_router(chat_router)
app.include_router(users_router)
app.include_router(admin_router)
app.include_router(twitch_auth_router)
app.include_router(backup_router)
app.include_router(settings_router)
app.include_router(roles_router)
app.include_router(route_info_router, prefix="/api", tags=["route-info"])

# SSE endpoint
app.get("/api/sse")(sse_endpoint)

# Set app reference for route info API
set_app(app)

# Mount static files for HLS, storage, and uploads
os.makedirs(settings.FFMPEG_CACHE_PATH, exist_ok=True)
os.makedirs(settings.STORAGE_PATH, exist_ok=True)
os.makedirs(settings.UPLOAD_PATH, exist_ok=True)
app.mount("/hls", StaticFiles(directory=settings.FFMPEG_CACHE_PATH), name="hls")
app.mount("/storage", StaticFiles(directory=settings.STORAGE_PATH), name="storage")
app.mount("/uploads", StaticFiles(directory=settings.UPLOAD_PATH), name="uploads")


@app.get("/")
async def root():
    return {
        "message": "StreamA2026 API",
        "version": "1.0.0",
        "docs": "/docs"
    }


# Permissions endpoints (moved from route_info.py to add auth protection)
@app.get("/api/permissions/endpoints")
async def get_endpoint_permissions_list(current_user = Depends(get_current_user)):
    """Get all endpoint permissions for analysis"""
    from app.api.route_info import get_endpoint_permissions
    return get_endpoint_permissions(app)


@app.get("/api/permissions/protected")
async def get_protected_endpoints(current_user = Depends(get_current_user)):
    """Get endpoints that require authentication"""
    from app.api.route_info import get_endpoint_permissions
    permissions = get_endpoint_permissions()
    return {
        key: value
        for key, value in permissions.items()
        if value["requires_auth"]
    }


@app.get("/api/permissions/admin")
async def get_admin_endpoints(current_user = Depends(get_current_admin_user)):
    """Get endpoints that require admin role"""
    from app.api.route_info import get_endpoint_permissions
    permissions = get_endpoint_permissions()
    return {
        key: value
        for key, value in permissions.items()
        if value["requires_admin"]
    }


@app.get("/api/permissions/public")
async def get_public_endpoints(current_user = Depends(get_current_user)):
    """Get endpoints that don't require authentication"""
    from app.api.route_info import get_endpoint_permissions
    permissions = get_endpoint_permissions()
    return {
        key: value
        for key, value in permissions.items()
        if not value["requires_auth"]
    }


@app.get("/api/events")
async def events():
    return sse_endpoint


@app.websocket("/ws/stream/{stream_id}")
async def websocket_endpoint(websocket: WebSocket, stream_id: str, token: str = None):
    from jose import JWTError, jwt
    from app.config import settings
    
    # Validate token
    if token:
        try:
            payload = jwt.decode(token, settings.JWT_SECRET_KEY, algorithms=[settings.JWT_ALGORITHM])
            user_id = payload.get("sub")
            if not user_id:
                await websocket.close(code=1008, reason="Invalid token")
                return
        except JWTError:
            await websocket.close(code=1008, reason="Invalid token")
            return
    
    try:
        stream_uuid = UUID(stream_id)
        await manager.connect(websocket, stream_uuid)
        try:
            while True:
                data = await websocket.receive_text()
                # Echo back or handle message
                await manager.send_personal_message(f"Received: {data}", websocket)
        except WebSocketDisconnect:
            manager.disconnect(websocket, stream_uuid)
    except ValueError:
        await websocket.close(code=1008, reason="Invalid stream ID")


@app.websocket("/ws")
async def global_websocket(websocket: WebSocket, token: str = None):
    from jose import JWTError, jwt
    from app.config import settings
    
    # Validate token
    if token:
        try:
            payload = jwt.decode(token, settings.JWT_SECRET_KEY, algorithms=[settings.JWT_ALGORITHM])
            user_id = payload.get("sub")
            if not user_id:
                await websocket.close(code=1008, reason="Invalid token")
                return
        except JWTError:
            await websocket.close(code=1008, reason="Invalid token")
            return
    
    await manager.connect(websocket)
    try:
        while True:
            data = await websocket.receive_text()
            await manager.send_personal_message(f"Received: {data}", websocket)
    except WebSocketDisconnect:
        manager.disconnect(websocket)


async def cleanup_expired_sessions():
    """Remove expired sessions from database"""
    from app.database import SessionLocal
    from app.models.session import Session as SessionModel
    from datetime import datetime, timezone
    
    db = SessionLocal()
    try:
        deleted = db.query(SessionModel).filter(
            SessionModel.expires_at < datetime.now(timezone.utc)
        ).delete()
        db.commit()
        if deleted > 0:
            logger.info(f"Cleaned up {deleted} expired sessions")
    except Exception as e:
        logger.error(f"Error cleaning up sessions: {e}")
        db.rollback()
    finally:
        db.close()


@app.on_event("startup")
async def startup_event():
    from app.daemon import StreamDaemon
    from apscheduler.schedulers.asyncio import AsyncIOScheduler
    import asyncio
    
    # Start stream daemon
    daemon = StreamDaemon()
    asyncio.create_task(daemon.start())
    
    # Start session cleanup scheduler
    scheduler = AsyncIOScheduler()
    scheduler.add_job(cleanup_expired_sessions, 'interval', hours=1)
    scheduler.start()
    logger.info("Started session cleanup scheduler (runs every hour)")


@app.exception_handler(Exception)
async def global_exception_handler(request, exc):
    logger.error(f"Unhandled exception: {exc}")
    return JSONResponse(
        status_code=500,
        content={"detail": "Internal server error"}
    )


if __name__ == "__main__":
    import uvicorn
    uvicorn.run(app, host="0.0.0.0", port=8000)
