import express from "express";
import path from "path";
import fs from "fs";
import crypto from "crypto";
import { createServer as createViteServer } from "vite";
import multer from "multer";

// ═══ Uploads directory ═══
const UPLOADS_DIR = process.env.BRADLY_UPLOADS_DIR || path.join(process.cwd(), "uploads");
if (!fs.existsSync(UPLOADS_DIR)) {
  fs.mkdirSync(UPLOADS_DIR, { recursive: true });
}

// ─── Multer: memory storage for transcription (existing) ───
const memoryUpload = multer({ storage: multer.memoryStorage() });

// ─── Multer: disk storage for persistent media uploads ───
const diskStorage = multer.diskStorage({
  destination: (_req, _file, cb) => cb(null, UPLOADS_DIR),
  filename: (_req, file, cb) => {
    const ext = path.extname(file.originalname) || '.bin';
    const id = crypto.randomUUID();
    cb(null, `${id}${ext}`);
  },
});

const mediaUpload = multer({
  storage: diskStorage,
  limits: {
    fileSize: 50 * 1024 * 1024, // 50 MB max
  },
  fileFilter: (_req, file, cb) => {
    const allowed = /^(image|video|audio)\//;
    if (allowed.test(file.mimetype)) {
      cb(null, true);
    } else {
      cb(new Error(`Unsupported file type: ${file.mimetype}`));
    }
  },
});

async function startServer() {
  const app = await createExpressApp();
  const PORT = 3000;
  app.listen(PORT, "0.0.0.0", () => {
    console.log(`Server running on http://localhost:${PORT}`);
  });
}

export async function createExpressApp() {
  const app = express();
  const PORT = 3000;

  // Add JSON parser with generous limit for render payloads (timelineElements can be large)
  app.use(express.json({ limit: '50mb' }));

  // ═══ Serve uploaded media files ═══
  app.use("/api/media", express.static(UPLOADS_DIR, {
    maxAge: "1d",
    immutable: true,
  }));

  // ═══ Upload media file (persistent storage) ═══
  app.post("/api/upload", mediaUpload.single("file"), (req, res) => {
    if (!req.file) {
      return res.status(400).json({ error: "No file provided" });
    }

    const url = `/api/media/${req.file.filename}`;
    console.log(`📁 Uploaded: ${req.file.originalname} → ${url} (${(req.file.size / 1024).toFixed(1)} KB)`);

    res.json({
      url,
      originalName: req.file.originalname,
      size: req.file.size,
      mimetype: req.file.mimetype,
    });
  });

  // ═══ Transcription (existing) ═══
  app.post("/api/transcribe", memoryUpload.single('file'), async (req, res) => {
    try {
      if (!req.file) {
        return res.status(400).json({ error: "No file provided" });
      }

      if (!process.env.GROQ_API_KEY) {
        return res.status(500).json({ error: "GROQ_API_KEY not configured" });
      }

      const blob = new Blob([req.file.buffer], { type: req.file.mimetype || "audio/mpeg" });
      const formData = new FormData();
      formData.append("file", blob, req.file.originalname || "audio.mp3");
      formData.append("model", "whisper-large-v3");
      formData.append("response_format", "verbose_json");
      formData.append("timestamp_granularities[]", "word");

      const response = await fetch("https://api.groq.com/openai/v1/audio/transcriptions", {
        method: "POST",
        headers: {
          Authorization: `Bearer ${process.env.GROQ_API_KEY}`,
        },
        body: formData,
      });

      if (!response.ok) {
        const err = await response.text();
        console.error("Groq API error:", err);
        return res.status(response.status).json({ error: err });
      }

      const data = await response.json();
      // Return both text and word-level timestamps
      res.json({
        text: data.text,
        words: data.words || [],  // [{ word, start, end }]
        segments: data.segments || [],
      });
    } catch (error) {
      console.error("Transcription error:", error);
      res.status(500).json({ error: "Internal server error" });
    }
  });

  // ═══ Stock Media Proxy (Pexels) ═══
  const PEXELS_API_KEY = process.env.PEXELS_API_KEY || '';

  app.get("/api/stock/photos", async (req, res) => {
    if (!PEXELS_API_KEY) {
      return res.status(501).json({ error: "PEXELS_API_KEY not configured" });
    }
    try {
      const { q, page = "1", per_page = "20" } = req.query;
      const endpoint = q
        ? `https://api.pexels.com/v1/search?query=${encodeURIComponent(String(q))}&page=${page}&per_page=${per_page}`
        : `https://api.pexels.com/v1/curated?page=${page}&per_page=${per_page}`;
      
      const response = await fetch(endpoint, {
        headers: { Authorization: PEXELS_API_KEY },
      });
      const data = await response.json();
      res.json(data);
    } catch (err: any) {
      res.status(500).json({ error: err.message });
    }
  });

  app.get("/api/stock/videos", async (req, res) => {
    if (!PEXELS_API_KEY) {
      return res.status(501).json({ error: "PEXELS_API_KEY not configured" });
    }
    try {
      const { q, page = "1", per_page = "15" } = req.query;
      const endpoint = q
        ? `https://api.pexels.com/videos/search?query=${encodeURIComponent(String(q))}&page=${page}&per_page=${per_page}`
        : `https://api.pexels.com/videos/popular?page=${page}&per_page=${per_page}`;

      const response = await fetch(endpoint, {
        headers: { Authorization: PEXELS_API_KEY },
      });
      const data = await response.json();
      res.json(data);
    } catch (err: any) {
      res.status(500).json({ error: err.message });
    }
  });

  // Download a stock file to uploads/ for persistent use
  app.post("/api/stock/download", async (req, res) => {
    try {
      const { url, filename } = req.body;
      if (!url || !filename) {
        return res.status(400).json({ error: "url and filename required" });
      }

      const ext = path.extname(filename) || '.jpg';
      const safeFilename = `stock-${crypto.randomUUID()}${ext}`;
      const outputPath = path.join(UPLOADS_DIR, safeFilename);

      const response = await fetch(url);
      if (!response.ok) throw new Error(`Download failed: ${response.status}`);

      const buffer = Buffer.from(await response.arrayBuffer());
      fs.writeFileSync(outputPath, buffer);

      res.json({ url: `/api/media/${safeFilename}` });
    } catch (err: any) {
      res.status(500).json({ error: err.message });
    }
  });

  // ═══ Render Queue ═══
  const RENDERS_DIR = process.env.BRADLY_RENDERS_DIR || path.join(process.cwd(), "renders");
  if (!fs.existsSync(RENDERS_DIR)) {
    fs.mkdirSync(RENDERS_DIR, { recursive: true });
  }

  // Serve rendered files
  app.use("/api/renders", express.static(RENDERS_DIR, {
    maxAge: "1h",
  }));

  // Lazy-load render queue (heavy dependencies)
  let renderQueue: typeof import("./src/server/renderQueue") | null = null;
  async function getRenderQueue() {
    if (!renderQueue) {
      renderQueue = await import("./src/server/renderQueue");
    }
    return renderQueue;
  }

  // Start a render job
  app.post("/api/render/start", async (req, res) => {
    try {
      const { format, width, height, fps, durationInFrames, compositionId, inputProps } = req.body;

      if (!format || !width || !height || !compositionId) {
        return res.status(400).json({ error: "Missing required fields: format, width, height, compositionId" });
      }

      const rq = await getRenderQueue();
      const job = rq.createJob({
        format,
        width,
        height,
        fps: fps || 30,
        durationInFrames: durationInFrames || 150,
        compositionId,
        inputProps: inputProps || {},
      });

      console.log(`🎬 Job created: ${job.id} (${format} ${width}x${height})`);
      res.json(job);
    } catch (err: any) {
      console.error("Render start error:", err);
      res.status(500).json({ error: err.message || "Failed to create render job" });
    }
  });

  // List all jobs
  app.get("/api/render/jobs", async (_req, res) => {
    try {
      const rq = await getRenderQueue();
      const jobs = rq.getAllJobs();
      // Strip inputProps (too large for list)
      const sanitized = jobs.map(({ inputProps, ...rest }) => rest);
      res.json(sanitized);
    } catch (err: any) {
      res.status(500).json({ error: err.message });
    }
  });

  // Get a single job by ID (used by batch exporter polling)
  app.get("/api/render/jobs/:id", async (req, res) => {
    try {
      const rq = await getRenderQueue();
      const job = rq.getJob(req.params.id);
      if (!job) {
        return res.status(404).json({ error: "Job not found" });
      }
      // Strip inputProps (too large)
      const { inputProps, ...rest } = job;
      res.json(rest);
    } catch (err: any) {
      res.status(500).json({ error: err.message });
    }
  });

  // Delete a job
  app.delete("/api/render/jobs/:id", async (req, res) => {
    try {
      const rq = await getRenderQueue();
      const deleted = rq.deleteJob(req.params.id);
      res.json({ deleted });
    } catch (err: any) {
      res.status(500).json({ error: err.message });
    }
  });

  // SSE — Real-time job progress
  app.get("/api/render/events", async (req, res) => {
    res.writeHead(200, {
      "Content-Type": "text/event-stream",
      "Cache-Control": "no-cache",
      Connection: "keep-alive",
      "X-Accel-Buffering": "no",
    });

    // Send initial heartbeat
    res.write("data: {\"type\":\"connected\"}\n\n");

    const clientId = crypto.randomUUID();
    const rq = await getRenderQueue();

    const cleanup = rq.addSSEClient(clientId, (data: string) => {
      res.write(`data: ${data}\n\n`);
    });

    // Heartbeat every 30s to keep connection alive
    const heartbeat = setInterval(() => {
      res.write(": heartbeat\n\n");
    }, 30000);

    req.on("close", () => {
      clearInterval(heartbeat);
      cleanup();
    });
  });

  if (!process.env.BRADLY_ELECTRON) {
    if (process.env.NODE_ENV !== "production") {
      const vite = await createViteServer({
        server: { middlewareMode: true },
        appType: "spa",
      });
      app.use(vite.middlewares);
    } else {
      const distPath = path.join(process.cwd(), "dist");
      app.use(express.static(distPath));
      app.get("*", (req, res) => {
        res.sendFile(path.join(distPath, "index.html"));
      });
    }
  }

  return app;
}

// Direct execution (web mode only)
if (!process.env.BRADLY_ELECTRON) {
  startServer();
}
