"""FastAPI application entry point."""

import sys
from contextlib import asynccontextmanager
from pathlib import Path

from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from fastapi.middleware.gzip import GZipMiddleware
from fastapi.responses import FileResponse, JSONResponse
from fastapi.staticfiles import StaticFiles
from fastapi.templating import Jinja2Templates
from starlette.requests import Request

from wled_controller import __version__, GITEA_BASE_URL, GITEA_REPO
from wled_controller.api import router
from wled_controller.api.dependencies import init_dependencies
from wled_controller.config import get_config
from wled_controller.core.processing.processor_manager import (
    ProcessorDependencies,
    ProcessorManager,
)
from wled_controller.storage import DeviceStore
from wled_controller.storage.template_store import TemplateStore
from wled_controller.storage.postprocessing_template_store import PostprocessingTemplateStore
from wled_controller.storage.picture_source_store import PictureSourceStore
from wled_controller.storage.output_target_store import OutputTargetStore
from wled_controller.storage.color_strip_store import ColorStripStore
from wled_controller.storage.audio_source_store import AudioSourceStore
from wled_controller.storage.audio_template_store import AudioTemplateStore
import wled_controller.core.audio  # noqa: F401 — trigger engine auto-registration
from wled_controller.storage.value_source_store import ValueSourceStore
from wled_controller.storage.automation_store import AutomationStore
from wled_controller.storage.scene_preset_store import ScenePresetStore
from wled_controller.storage.sync_clock_store import SyncClockStore
from wled_controller.storage.color_strip_processing_template_store import (
    ColorStripProcessingTemplateStore,
)
from wled_controller.storage.gradient_store import GradientStore
from wled_controller.storage.weather_source_store import WeatherSourceStore
from wled_controller.storage.asset_store import AssetStore
from wled_controller.core.processing.sync_clock_manager import SyncClockManager
from wled_controller.core.weather.weather_manager import WeatherManager
from wled_controller.storage.home_assistant_store import HomeAssistantStore
from wled_controller.core.home_assistant.ha_manager import HomeAssistantManager
from wled_controller.core.automations.automation_engine import AutomationEngine
from wled_controller.storage.game_integration_store import GameIntegrationStore
from wled_controller.core.game_integration.event_bus import GameEventBus
import wled_controller.core.game_integration.adapters  # noqa: F401 — register built-in adapters
from wled_controller.core.game_integration.community_loader import register_community_adapters
from wled_controller.core.mqtt.mqtt_service import MQTTService
from wled_controller.core.mqtt.mqtt_manager import MQTTManager
from wled_controller.storage.mqtt_source_store import MQTTSourceStore
from wled_controller.storage.audio_processing_template_store import AudioProcessingTemplateStore
import wled_controller.core.audio.filters  # noqa: F401 — trigger audio filter auto-registration
from wled_controller.core.devices.mqtt_client import set_mqtt_service
from wled_controller.core.backup.auto_backup import AutoBackupEngine
from wled_controller.core.processing.os_notification_listener import OsNotificationListener
from wled_controller.core.update.update_service import UpdateService
from wled_controller.core.update.gitea_provider import GiteaReleaseProvider
from wled_controller.storage.database import Database
from wled_controller.utils import setup_logging, get_logger, install_broadcast_handler

# Initialize logging
setup_logging()
install_broadcast_handler()
logger = get_logger(__name__)

# Get configuration
config = get_config()

# Initialize SQLite database
db = Database(config.storage.database_file)

# Seed demo data after DB is ready (first-run only)
if config.demo:
    from wled_controller.core.demo_seed import seed_demo_data

    seed_demo_data(db)

# Initialize storage and processing
device_store = DeviceStore(db)
template_store = TemplateStore(db)
pp_template_store = PostprocessingTemplateStore(db)
picture_source_store = PictureSourceStore(db)
output_target_store = OutputTargetStore(db)
color_strip_store = ColorStripStore(db)
audio_source_store = AudioSourceStore(db)
audio_template_store = AudioTemplateStore(db)
value_source_store = ValueSourceStore(db)
automation_store = AutomationStore(db)
scene_preset_store = ScenePresetStore(db)
sync_clock_store = SyncClockStore(db)
cspt_store = ColorStripProcessingTemplateStore(db)
gradient_store = GradientStore(db)
gradient_store.migrate_palette_references(color_strip_store)
weather_source_store = WeatherSourceStore(db)
asset_store = AssetStore(db, config.assets.assets_dir)

# Import prebuilt notification sounds on first run
_prebuilt_sounds_dir = Path(__file__).parent / "data" / "prebuilt_sounds"
asset_store.import_prebuilt_sounds(_prebuilt_sounds_dir)

sync_clock_manager = SyncClockManager(sync_clock_store)
weather_manager = WeatherManager(weather_source_store)
ha_store = HomeAssistantStore(db)
ha_manager = HomeAssistantManager(ha_store)
mqtt_source_store = MQTTSourceStore(db)
audio_processing_template_store = AudioProcessingTemplateStore(db)
game_integration_store = GameIntegrationStore(db)
game_event_bus = GameEventBus()
register_community_adapters()

processor_manager = ProcessorManager(
    ProcessorDependencies(
        picture_source_store=picture_source_store,
        capture_template_store=template_store,
        pp_template_store=pp_template_store,
        device_store=device_store,
        color_strip_store=color_strip_store,
        audio_source_store=audio_source_store,
        value_source_store=value_source_store,
        audio_template_store=audio_template_store,
        sync_clock_manager=sync_clock_manager,
        cspt_store=cspt_store,
        gradient_store=gradient_store,
        weather_manager=weather_manager,
        asset_store=asset_store,
        ha_manager=ha_manager,
        game_event_bus=game_event_bus,
        audio_processing_template_store=audio_processing_template_store,
    )
)


def _save_all_stores() -> None:
    """Shutdown hook — SQLite stores use write-through caching, so this is a no-op.

    Every create/update/delete already goes to the database immediately.
    Kept for backward compatibility with server_ref.py which calls this.
    """
    logger.info("Shutdown: all stores already persisted (write-through cache)")


@asynccontextmanager
async def lifespan(app: FastAPI):
    """Application lifespan manager.

    Handles startup and shutdown events.
    """
    # Startup
    logger.info(f"Starting LED Grab v{__version__}")
    logger.info(f"Python version: {sys.version}")
    logger.info(f"Server listening on {config.server.host}:{config.server.port}")
    print("\n  =============================================")
    print(f"   LED Grab v{__version__}")
    print(f"   Open http://localhost:{config.server.port} in your browser")
    print("  =============================================\n")

    # Log authentication mode
    if not config.auth.api_keys:
        logger.info("Authentication disabled (no API keys configured)")
    else:
        logger.info(f"Authentication enabled ({len(config.auth.api_keys)} API key(s) configured)")
        client_labels = ", ".join(config.auth.api_keys.keys())
        logger.info(f"Authorized clients: {client_labels}")

    # Create MQTT service (shared broker connection — legacy, used by MQTTLEDClient)
    mqtt_service = MQTTService(config.mqtt)
    set_mqtt_service(mqtt_service)

    # Create MQTT manager (multi-source, ref-counted — new entity-based model)
    mqtt_manager = MQTTManager(mqtt_source_store)

    # Create automation engine (needs processor_manager + mqtt + stores for scene activation)
    automation_engine = AutomationEngine(
        automation_store,
        processor_manager,
        mqtt_service=mqtt_service,
        scene_preset_store=scene_preset_store,
        target_store=output_target_store,
        device_store=device_store,
        ha_manager=ha_manager,
        mqtt_manager=mqtt_manager,
    )

    # Create auto-backup engine — derive paths from database location so that
    # demo mode auto-backups go to data/demo/ instead of data/.
    _data_dir = Path(config.storage.database_file).parent
    auto_backup_engine = AutoBackupEngine(
        backup_dir=_data_dir / "backups",
        db=db,
    )

    # Create update service (checks for new releases)
    _release_provider = GiteaReleaseProvider(
        base_url=GITEA_BASE_URL,
        repo=GITEA_REPO,
    )
    update_service = UpdateService(
        provider=_release_provider,
        db=db,
        fire_event=processor_manager.fire_event,
        update_dir=_data_dir / "updates",
    )

    # Initialize API dependencies
    init_dependencies(
        device_store,
        template_store,
        processor_manager,
        database=db,
        pp_template_store=pp_template_store,
        picture_source_store=picture_source_store,
        output_target_store=output_target_store,
        color_strip_store=color_strip_store,
        audio_source_store=audio_source_store,
        audio_template_store=audio_template_store,
        value_source_store=value_source_store,
        automation_store=automation_store,
        scene_preset_store=scene_preset_store,
        automation_engine=automation_engine,
        auto_backup_engine=auto_backup_engine,
        sync_clock_store=sync_clock_store,
        sync_clock_manager=sync_clock_manager,
        cspt_store=cspt_store,
        gradient_store=gradient_store,
        weather_source_store=weather_source_store,
        weather_manager=weather_manager,
        update_service=update_service,
        asset_store=asset_store,
        ha_store=ha_store,
        ha_manager=ha_manager,
        game_integration_store=game_integration_store,
        game_event_bus=game_event_bus,
        mqtt_store=mqtt_source_store,
        mqtt_manager=mqtt_manager,
        audio_processing_template_store=audio_processing_template_store,
    )

    # Register devices in processor manager for health monitoring
    devices = device_store.get_all_devices()
    for device in devices:
        try:
            processor_manager.add_device(
                device_id=device.id,
                device_url=device.url,
                led_count=device.led_count,
                device_type=device.device_type,
                baud_rate=device.baud_rate,
                software_brightness=device.software_brightness,
                auto_shutdown=device.auto_shutdown,
            )
            logger.info(f"Registered device: {device.name} ({device.id})")
        except Exception as e:
            logger.error(f"Failed to register device {device.id}: {e}")

    logger.info(f"Registered {len(devices)} devices for health monitoring")

    # Register output targets in processor manager
    targets = output_target_store.get_all_targets()
    registered_targets = 0
    for target in targets:
        try:
            target.register_with_manager(processor_manager)
            registered_targets += 1
            logger.info(f"Registered target: {target.name} ({target.id})")
        except Exception as e:
            logger.error(f"Failed to register target {target.id}: {e}")

    logger.info(f"Registered {registered_targets} output target(s)")

    # Start background health monitoring for all devices
    await processor_manager.start_health_monitoring()

    # Start MQTT service (broker connection for output, triggers, state)
    await mqtt_service.start()

    # Start automation engine (evaluates conditions and activates scenes)
    await automation_engine.start()

    # Start auto-backup engine (periodic configuration backups)
    await auto_backup_engine.start()

    # Start update checker (periodic release polling)
    await update_service.start()

    # Start OS notification listener (Windows toast → notification CSS streams)
    os_notif_listener = OsNotificationListener(
        color_strip_store=color_strip_store,
        color_strip_stream_manager=processor_manager.color_strip_stream_manager,
    )
    os_notif_listener.start()

    yield

    # Shutdown
    logger.info("Shutting down LED Grab")

    # Persist all stores to disk before stopping anything.
    # This ensures in-memory data survives force-kills and restarts
    # where no CRUD happened during the session.
    _save_all_stores()

    # Stop Home Assistant manager
    try:
        await ha_manager.shutdown()
    except Exception as e:
        logger.error(f"Error stopping Home Assistant manager: {e}")

    # Stop weather manager
    try:
        weather_manager.shutdown()
    except Exception as e:
        logger.error(f"Error stopping weather manager: {e}")

    # Stop update checker
    try:
        await update_service.stop()
    except Exception as e:
        logger.error(f"Error stopping update checker: {e}")

    # Stop auto-backup engine
    try:
        await auto_backup_engine.stop()
    except Exception as e:
        logger.error(f"Error stopping auto-backup engine: {e}")

    # Stop automation engine first (deactivates automation-managed scenes)
    try:
        await automation_engine.stop()
        logger.info("Stopped automation engine")
    except Exception as e:
        logger.error(f"Error stopping automation engine: {e}")

    # Stop OS notification listener
    try:
        os_notif_listener.stop()
    except Exception as e:
        logger.error(f"Error stopping OS notification listener: {e}")

    # Stop all processing
    try:
        await processor_manager.stop_all()
        logger.info("Stopped all processors")
    except Exception as e:
        logger.error(f"Error stopping processors: {e}")

    # Stop MQTT manager (entity-based broker connections)
    try:
        await mqtt_manager.shutdown()
    except Exception as e:
        logger.error(f"Error stopping MQTT manager: {e}")

    # Stop MQTT service (legacy global connection)
    try:
        await mqtt_service.stop()
    except Exception as e:
        logger.error(f"Error stopping MQTT service: {e}")


# Create FastAPI application
app = FastAPI(
    title="LED Grab",
    description="Control WLED devices based on screen content for ambient lighting",
    version=__version__,
    lifespan=lifespan,
    docs_url="/docs",
    redoc_url="/redoc",
    openapi_url="/openapi.json",
)

# Configure CORS
app.add_middleware(
    CORSMiddleware,
    allow_origins=config.server.cors_origins,
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)
app.add_middleware(GZipMiddleware, minimum_size=500)

# Include API routes
app.include_router(router)

# PWA: serve manifest and service worker from root scope
_static_root = Path(__file__).parent / "static"


@app.get("/manifest.json", include_in_schema=False)
async def pwa_manifest():
    """Serve PWA manifest from root scope."""
    return FileResponse(_static_root / "manifest.json", media_type="application/manifest+json")


@app.get("/sw.js", include_in_schema=False)
async def pwa_service_worker():
    """Serve service worker from root scope (controls all pages)."""
    return FileResponse(
        _static_root / "sw.js",
        media_type="application/javascript",
        headers={"Cache-Control": "no-cache"},
    )


# Middleware: no-cache for static JS/CSS (development convenience)
@app.middleware("http")
async def _no_cache_static(request: Request, call_next):
    path = request.url.path
    if path.startswith("/static/") and path.endswith((".js", ".css", ".json")):
        response = await call_next(request)
        response.headers["Cache-Control"] = "no-cache, must-revalidate"
        return response
    return await call_next(request)


# Mount static files
static_path = Path(__file__).parent / "static"
if static_path.exists():
    app.mount("/static", StaticFiles(directory=str(static_path)), name="static")
    logger.info(f"Mounted static files from {static_path}")
else:
    logger.warning(f"Static files directory not found: {static_path}")

# Jinja2 templates
templates_path = Path(__file__).parent / "templates"
templates = Jinja2Templates(directory=str(templates_path))


@app.exception_handler(Exception)
async def global_exception_handler(request, exc):
    """Global exception handler for unhandled errors."""
    import uuid

    ref_id = uuid.uuid4().hex[:8]
    logger.error("Unhandled exception [ref=%s]: %s", ref_id, exc, exc_info=True)

    return JSONResponse(
        status_code=500,
        content={
            "error": "InternalServerError",
            "message": "Internal server error",
            "ref": ref_id,
        },
    )


@app.get("/")
async def root(request: Request):
    """Serve the web UI dashboard."""
    return templates.TemplateResponse(request, "index.html")


if __name__ == "__main__":
    import uvicorn

    uvicorn.run(
        "wled_controller.main:app",
        host=config.server.host,
        port=config.server.port,
        log_level=config.server.log_level.lower(),
        reload=False,  # Disabled due to watchfiles infinite reload loop
    )
