import express from "express";
import session from "express-session";
import RedisStore from "connect-redis";
import cors from "cors";
import helmet from "helmet";
import { config } from "./config";
import { redisClient } from "./utils/redis";
import { prisma } from "./utils/db";

import authRoutes from "./routes/auth";
import onboardingRoutes from "./routes/onboarding";
import libraryRoutes from "./routes/library";
import playsRoutes from "./routes/plays";
import settingsRoutes from "./routes/settings";
import systemSettingsRoutes from "./routes/systemSettings";
import listeningStateRoutes from "./routes/listeningState";
import playbackStateRoutes from "./routes/playbackState";
import offlineRoutes from "./routes/offline";
import playlistsRoutes from "./routes/playlists";
import searchRoutes from "./routes/search";
import recommendationsRoutes from "./routes/recommendations";
import downloadsRoutes from "./routes/downloads";
import webhooksRoutes from "./routes/webhooks";
import audiobooksRoutes from "./routes/audiobooks";
import podcastsRoutes from "./routes/podcasts";
import artistsRoutes from "./routes/artists";
import soulseekRoutes from "./routes/soulseek";
import discoverRoutes from "./routes/discover";
import apiKeysRoutes from "./routes/apiKeys";
import mixesRoutes from "./routes/mixes";
import enrichmentRoutes from "./routes/enrichment";
import homepageRoutes from "./routes/homepage";
import deviceLinkRoutes from "./routes/deviceLink";
import spotifyRoutes from "./routes/spotify";
import notificationsRoutes from "./routes/notifications";
import browseRoutes from "./routes/browse";
import analysisRoutes from "./routes/analysis";
import releasesRoutes from "./routes/releases";
import { dataCacheService } from "./services/dataCache";
import { errorHandler } from "./middleware/errorHandler";
import {
    authLimiter,
    apiLimiter,
    streamLimiter,
    imageLimiter,
} from "./middleware/rateLimiter";
import swaggerUi from "swagger-ui-express";
import { swaggerSpec } from "./config/swagger";

const app = express();

// Middleware
app.use(
    helmet({
        crossOriginResourcePolicy: { policy: "cross-origin" },
    })
);
app.use(
    cors({
        origin: (origin, callback) => {
            // For self-hosted apps: allow all origins by default
            // Users deploy on their own domains/IPs - we can't predict them
            // Security is handled by authentication, not CORS
            if (!origin) {
                // Allow requests with no origin (same-origin, curl, etc.)
                callback(null, true);
            } else if (
                config.allowedOrigins === true ||
                config.nodeEnv === "development"
            ) {
                // Explicitly allow all origins
                callback(null, true);
            } else if (
                Array.isArray(config.allowedOrigins) &&
                config.allowedOrigins.length > 0
            ) {
                // Check against specific allowed origins if configured
                if (config.allowedOrigins.includes(origin)) {
                    callback(null, true);
                } else {
                    // For self-hosted: allow anyway but log it
                    // Users shouldn't have to configure CORS for their own app
                    console.log(
                        `[CORS] Origin ${origin} not in allowlist, allowing anyway (self-hosted)`
                    );
                    callback(null, true);
                }
            } else {
                // No restrictions - allow all (self-hosted default)
                callback(null, true);
            }
        },
        credentials: true,
    })
);
app.use(express.json({ limit: "1mb" })); // Increased from 100KB default to support large queue payloads

// Session
// Trust proxy for reverse proxy setups (nginx, traefik, etc.)
app.set("trust proxy", 1);

app.use(
    session({
        store: new RedisStore({
            client: redisClient,
            ttl: 7 * 24 * 60 * 60, // 7 days in seconds - must match cookie maxAge
        }),
        secret: config.sessionSecret,
        resave: false,
        saveUninitialized: false,
        proxy: true, // Trust the reverse proxy
        cookie: {
            httpOnly: true,
            // For self-hosted apps: allow HTTP access (common for LAN deployments)
            // If behind HTTPS reverse proxy, the proxy should handle security
            secure: false,
            sameSite: "lax",
            maxAge: 1000 * 60 * 60 * 24 * 7, // 7 days
        },
    })
);

// Routes - All API routes prefixed with /api for clear separation from frontend
// Apply rate limiting to auth routes
app.use("/api/auth/login", authLimiter);
app.use("/api/auth/register", authLimiter);
app.use("/api/auth", authRoutes);
app.use("/api/onboarding", onboardingRoutes); // Public onboarding routes

// Apply general API rate limiting to all API routes
app.use("/api/api-keys", apiLimiter, apiKeysRoutes);
app.use("/api/device-link", apiLimiter, deviceLinkRoutes);
// NOTE: /api/library has its own rate limiting (imageLimiter for cover-art, apiLimiter for others)
app.use("/api/library", libraryRoutes);
app.use("/api/plays", apiLimiter, playsRoutes);
app.use("/api/settings", apiLimiter, settingsRoutes);
app.use("/api/system-settings", apiLimiter, systemSettingsRoutes);
app.use("/api/listening-state", apiLimiter, listeningStateRoutes);
app.use("/api/playback-state", playbackStateRoutes); // No rate limit - syncs frequently
app.use("/api/offline", apiLimiter, offlineRoutes);
app.use("/api/playlists", apiLimiter, playlistsRoutes);
app.use("/api/search", apiLimiter, searchRoutes);
app.use("/api/recommendations", apiLimiter, recommendationsRoutes);
app.use("/api/downloads", apiLimiter, downloadsRoutes);
app.use("/api/notifications", apiLimiter, notificationsRoutes);
app.use("/api/webhooks", webhooksRoutes); // Webhooks should not be rate limited
// NOTE: /api/audiobooks has its own rate limiting (imageLimiter for covers, apiLimiter for others)
app.use("/api/audiobooks", audiobooksRoutes);
app.use("/api/podcasts", apiLimiter, podcastsRoutes);
app.use("/api/artists", apiLimiter, artistsRoutes);
app.use("/api/soulseek", apiLimiter, soulseekRoutes);
app.use("/api/discover", apiLimiter, discoverRoutes);
app.use("/api/mixes", apiLimiter, mixesRoutes);
app.use("/api/enrichment", apiLimiter, enrichmentRoutes);
app.use("/api/homepage", apiLimiter, homepageRoutes);
app.use("/api/spotify", apiLimiter, spotifyRoutes);
app.use("/api/browse", apiLimiter, browseRoutes);
app.use("/api/analysis", apiLimiter, analysisRoutes);
app.use("/api/releases", apiLimiter, releasesRoutes);

// Health check (keep at root for simple container health checks)
app.get("/health", (req, res) => {
    res.json({ status: "ok" });
});
app.get("/api/health", (req, res) => {
    res.json({ status: "ok" });
});

// Swagger API Documentation
app.use(
    "/api/docs",
    swaggerUi.serve,
    swaggerUi.setup(swaggerSpec, {
        customCss: ".swagger-ui .topbar { display: none }",
        customSiteTitle: "Lidify API Documentation",
    })
);

// Serve raw OpenAPI spec
app.get("/api/docs.json", (req, res) => {
    res.json(swaggerSpec);
});

// Error handler
app.use(errorHandler);

app.listen(config.port, "0.0.0.0", async () => {
    console.log(
        `Lidify API running on port ${config.port} (accessible on all network interfaces)`
    );

    // Enable slow query monitoring in development
    if (config.nodeEnv === "development") {
        const { enableSlowQueryMonitoring } = await import(
            "./utils/queryMonitor"
        );
        enableSlowQueryMonitoring();
    }

    // Initialize music configuration (reads from SystemSettings)
    const { initializeMusicConfig } = await import("./config");
    await initializeMusicConfig();

    // Initialize Bull queue workers
    await import("./workers");

    // Set up Bull Board dashboard
    const { createBullBoard } = await import("@bull-board/api");
    const { BullAdapter } = await import("@bull-board/api/bullAdapter");
    const { ExpressAdapter } = await import("@bull-board/express");
    const { scanQueue, discoverQueue, imageQueue } = await import(
        "./workers/queues"
    );

    const serverAdapter = new ExpressAdapter();
    serverAdapter.setBasePath("/api/admin/queues");

    createBullBoard({
        queues: [
            new BullAdapter(scanQueue),
            new BullAdapter(discoverQueue),
            new BullAdapter(imageQueue),
        ],
        serverAdapter,
    });

    app.use("/api/admin/queues", serverAdapter.getRouter());
    console.log("Bull Board dashboard available at /api/admin/queues");

    // Note: Native library scanning is now triggered manually via POST /library/scan
    // No automatic sync on startup - user must manually scan their music folder

    // Enrichment worker enabled for OWNED content only
    // - Background enrichment: Genres, MBIDs, similar artists for owned albums/artists
    // - On-demand fetching: Artist images, bios when browsing (cached in Redis 7 days)
    console.log(
        "Background enrichment enabled for owned content (genres, MBIDs, etc.)"
    );

    // Warm up Redis cache from database on startup
    // This populates Redis with existing artist images and album covers
    // so first page loads are instant instead of waiting for cache population
    dataCacheService.warmupCache().catch((err) => {
        console.error("Cache warmup failed:", err);
    });

    // Podcast cache cleanup - runs daily to remove cached episodes older than 30 days
    const { cleanupExpiredCache } = await import("./services/podcastDownload");

    // Run cleanup on startup (async, don't block)
    cleanupExpiredCache().catch((err) => {
        console.error("Podcast cache cleanup failed:", err);
    });

    // Schedule daily cleanup (every 24 hours)
    const TWENTY_FOUR_HOURS = 24 * 60 * 60 * 1000;
    setInterval(() => {
        cleanupExpiredCache().catch((err) => {
            console.error("Scheduled podcast cache cleanup failed:", err);
        });
    }, TWENTY_FOUR_HOURS);
    console.log("Podcast cache cleanup scheduled (daily, 30-day expiry)");
});

// Graceful shutdown handling
let isShuttingDown = false;

async function gracefulShutdown(signal: string) {
    if (isShuttingDown) {
        console.log("Shutdown already in progress...");
        return;
    }

    isShuttingDown = true;
    console.log(`\nReceived ${signal}. Starting graceful shutdown...`);

    try {
        // Shutdown workers (intervals, crons, queues)
        const { shutdownWorkers } = await import("./workers");
        await shutdownWorkers();

        // Close Redis connection
        console.log("Closing Redis connection...");
        await redisClient.quit();

        // Close Prisma connection
        console.log("Closing database connection...");
        await prisma.$disconnect();

        console.log("Graceful shutdown complete");
        process.exit(0);
    } catch (error) {
        console.error("Error during shutdown:", error);
        process.exit(1);
    }
}

// Handle termination signals
process.on("SIGTERM", () => gracefulShutdown("SIGTERM"));
process.on("SIGINT", () => gracefulShutdown("SIGINT"));
