import { Router } from 'express';
import { publicUrl } from '../lib/minio.js';

const router = Router();

// POST /api/videos — create a new video
router.post('/', async (req, res) => {
  const pool = req.app.get('pool');
  const { title, gnommo_id, course_code, status = 'draft', description, hook } = req.body;
  if (!title) return res.status(400).json({ error: 'title is required' });

  const gnommoId = gnommo_id || `editor-${Date.now()}`;

  try {
    const result = await pool.query(
      `INSERT INTO videos (gnommo_id, title, course_code, status, description, hook)
       VALUES ($1, $2, $3, $4, $5, $6)
       RETURNING *`,
      [gnommoId, title, course_code || null, status, description || null, hook || null]
    );
    res.status(201).json({ video: result.rows[0] });
  } catch (err) {
    if (err.code === '23505') {
      return res.status(409).json({ error: 'A video with that gnommo_id already exists' });
    }
    console.error('Create video error:', err);
    res.status(500).json({ error: 'Failed to create video' });
  }
});

// GET /api/videos — list all videos with slide count
router.get('/', async (req, res) => {
  const pool = req.app.get('pool');
  try {
    const result = await pool.query(`
      SELECT v.id, v.gnommo_id, v.title, v.status, v.course_code,
             COUNT(s.id)::int AS slide_count
      FROM videos v
      LEFT JOIN slides s ON s.video_id = v.id
      GROUP BY v.id
      ORDER BY v.id
    `);
    res.json({ videos: result.rows });
  } catch (err) {
    console.error('List videos error:', err);
    res.status(500).json({ error: 'Failed to fetch videos' });
  }
});

// GET /api/videos/:id — video metadata + slides + narration segments
router.get('/:id', async (req, res) => {
  const pool = req.app.get('pool');
  const { id } = req.params;
  try {
    const videoResult = await pool.query('SELECT * FROM videos WHERE id = $1', [id]);
    if (videoResult.rows.length === 0) {
      return res.status(404).json({ error: 'Video not found' });
    }

    const slidesResult = await pool.query(
      'SELECT * FROM slides WHERE video_id = $1 ORDER BY slide_order',
      [id]
    );

    const segmentsResult = await pool.query(
      `SELECT ns.*,
              COALESCE(sa_id.original_filename, sa_key.original_filename) AS asset_name,
              COALESCE(sa_id.duration_seconds,  sa_key.duration_seconds)  AS asset_duration,
              sa_id.parent_id                                              AS asset_parent_id
       FROM narration_segments ns
       LEFT JOIN shared_assets sa_id  ON ns.asset_id IS NOT NULL     AND sa_id.id              = ns.asset_id
       LEFT JOIN shared_assets sa_key ON ns.asset_id IS NULL         AND sa_key.minio_object_key = ns.minio_object_key
       WHERE ns.project_id = $1
       ORDER BY ns.segment_type, ns.sort_order`,
      [id]
    );

    const narrationSegments = segmentsResult.rows.map(seg => ({
      ...seg,
      url: seg.minio_object_key ? publicUrl(seg.minio_object_key) : null,
    }));

    res.json({
      video: videoResult.rows[0],
      slides: slidesResult.rows,
      narrationSegments,
    });
  } catch (err) {
    console.error('Get video error:', err);
    res.status(500).json({ error: 'Failed to fetch video' });
  }
});

// PUT /api/videos/:id — update video metadata
router.put('/:id', async (req, res) => {
  const pool = req.app.get('pool');
  const { id } = req.params;
  const { title, description, hook, status, youtube_url, duration_seconds, course_code } = req.body;
  try {
    const result = await pool.query(
      `UPDATE videos SET
        title            = COALESCE($1, title),
        description      = COALESCE($2, description),
        hook             = COALESCE($3, hook),
        status           = COALESCE($4, status),
        youtube_url      = COALESCE($5, youtube_url),
        duration_seconds = COALESCE($6, duration_seconds),
        course_code      = COALESCE($7, course_code),
        updated_at       = NOW()
      WHERE id = $8 RETURNING *`,
      [title, description, hook, status, youtube_url, duration_seconds, course_code, id]
    );
    if (result.rows.length === 0) {
      return res.status(404).json({ error: 'Video not found' });
    }
    res.json({ video: result.rows[0] });
  } catch (err) {
    console.error('Update video error:', err);
    res.status(500).json({ error: 'Failed to update video' });
  }
});

// DELETE /api/videos/:id
router.delete('/:id', async (req, res) => {
  const pool = req.app.get('pool');
  const { id } = req.params;
  try {
    const result = await pool.query('DELETE FROM videos WHERE id = $1 RETURNING id', [id]);
    if (result.rows.length === 0) return res.status(404).json({ error: 'Video not found' });
    res.json({ success: true });
  } catch (err) {
    console.error('Delete video error:', err);
    res.status(500).json({ error: 'Failed to delete video' });
  }
});

// POST /api/videos/:id/slides — add a slide
router.post('/:id/slides', async (req, res) => {
  const pool = req.app.get('pool');
  const { id } = req.params;
  const {
    component_key, props,
    display_mode, presenter_notes,
    // legacy / gnommo-ingest path
    asset_id, image_filename,
  } = req.body;

  try {
    // Determine next slide number (never reuse deleted numbers)
    const orderResult = await pool.query(
      `SELECT COALESCE(MAX(
        CASE WHEN gnommo_slide_id ~ '^S[0-9]+$'
             THEN CAST(SUBSTRING(gnommo_slide_id FROM 2) AS INTEGER)
             ELSE 0 END
      ), 0) + 1 AS next_num
      FROM slides WHERE video_id = $1`,
      [id]
    );
    const slideNum  = orderResult.rows[0].next_num;
    const gnommoSlideId = `S${slideNum}`;

    // Resolve display_mode: explicit > template default > fallback
    const resolvedMode = display_mode || 'fullscreen';

    // Legacy: resolve image filename from asset if provided
    let filename = image_filename || null;
    if (asset_id && !component_key) {
      const assetResult = await pool.query(
        'SELECT minio_object_key, original_filename FROM shared_assets WHERE id = $1',
        [asset_id]
      );
      if (assetResult.rows.length > 0) {
        const asset = assetResult.rows[0];
        filename = asset.minio_object_key ? publicUrl(asset.minio_object_key) : asset.original_filename;
      }
    }

    const result = await pool.query(
      `INSERT INTO slides
         (video_id, gnommo_slide_id, slide_order, image_filename,
          display_mode, presenter_notes, component_key, props)
       VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
       RETURNING *`,
      [
        id, gnommoSlideId, slideNum, filename,
        resolvedMode, presenter_notes || null,
        component_key || null,
        props != null ? JSON.stringify(props) : null,
      ]
    );
    res.status(201).json({ slide: result.rows[0] });
  } catch (err) {
    console.error('Create slide error:', err);
    res.status(500).json({ error: 'Failed to create slide' });
  }
});

// POST /api/videos/:id/narration-segments
// Body: { asset_ids: [id,...], segment_type: 'raw'|'final' }
//   or: { asset_id: id, segment_type }   (single, backwards-compat)
// Assets are inserted in the order supplied; caller sorts by filename first.
router.post('/:id/narration-segments', async (req, res) => {
  const pool = req.app.get('pool');
  const { id } = req.params;
  const { asset_id, asset_ids, segment_type = 'raw', skip_seconds, take_seconds } = req.body;

  const idsToAdd = asset_ids ?? (asset_id != null ? [asset_id] : []);
  if (idsToAdd.length === 0) return res.status(400).json({ error: 'asset_id or asset_ids required' });

  const keyPrefix = segment_type === 'final' ? 'Final' : 'Segment';

  const client = await pool.connect();
  try {
    // Max sort_order for this type
    const orderRow = await client.query(
      `SELECT COALESCE(MAX(sort_order), -1) AS max_order
       FROM narration_segments WHERE project_id = $1 AND segment_type = $2`,
      [id, segment_type]
    );
    let nextOrder = orderRow.rows[0].max_order + 1;

    // Max key number across all types (keys must be unique per project)
    const keyRow = await client.query(
      `SELECT COALESCE(MAX(
         CAST(REGEXP_REPLACE(segment_key, '[^0-9]', '', 'g') AS INTEGER)
       ), 0) AS max_num
       FROM narration_segments WHERE project_id = $1`,
      [id]
    );
    let nextKeyNum = keyRow.rows[0].max_num + 1;

    const assetsResult = await client.query(
      'SELECT id, minio_object_key, original_filename FROM shared_assets WHERE id = ANY($1)',
      [idsToAdd]
    );
    const assetMap = Object.fromEntries(assetsResult.rows.map(a => [String(a.id), a]));

    const created = [];
    await client.query('BEGIN');
    for (const aid of idsToAdd) {
      const asset = assetMap[String(aid)];
      if (!asset) continue;
      const segmentKey = `${keyPrefix}${String(nextKeyNum).padStart(3, '0')}`;
      const row = await client.query(
        `INSERT INTO narration_segments
           (project_id, segment_key, segment_type, source_file,
            minio_object_key, asset_id, sort_order, skip_seconds, take_seconds)
         VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9) RETURNING *`,
        [id, segmentKey, segment_type, asset.original_filename,
         asset.minio_object_key, asset.id, nextOrder,
         skip_seconds ?? null, take_seconds ?? null]
      );
      created.push({
        ...row.rows[0],
        asset_name: asset.original_filename,
        url: asset.minio_object_key ? publicUrl(asset.minio_object_key) : null,
      });
      nextOrder++; nextKeyNum++;
    }
    await client.query('COMMIT');
    res.status(201).json({ segments: created });
  } catch (err) {
    await client.query('ROLLBACK');
    console.error('Create narration segments error:', err);
    res.status(500).json({ error: 'Failed to create narration segments' });
  } finally {
    client.release();
  }
});

// PUT /api/videos/:id/narration-segments/reorder
// Body: { ids: ['uuid1','uuid2',...] }  — ordered array, updates sort_order by position.
router.put('/:id/narration-segments/reorder', async (req, res) => {
  const pool = req.app.get('pool');
  const { id } = req.params;
  const { ids } = req.body;
  if (!Array.isArray(ids)) return res.status(400).json({ error: 'ids must be an array' });

  const client = await pool.connect();
  try {
    await client.query('BEGIN');
    for (let i = 0; i < ids.length; i++) {
      await client.query(
        'UPDATE narration_segments SET sort_order = $1 WHERE id = $2 AND project_id = $3',
        [i, ids[i], id]
      );
    }
    await client.query('COMMIT');
    res.json({ success: true });
  } catch (err) {
    await client.query('ROLLBACK');
    console.error('Reorder segments error:', err);
    res.status(500).json({ error: 'Failed to reorder' });
  } finally {
    client.release();
  }
});

// POST /api/videos/:id/import-manuscript — bulk-create slides from a scripted manuscript
// Parses [S1], [S2]… markers; text between markers becomes presenter_notes.
// Uses ON CONFLICT to update notes if the slide already exists (safe to re-import).
router.post('/:id/import-manuscript', async (req, res) => {
  const pool = req.app.get('pool');
  const { id } = req.params;
  const { manuscript, component_key = 'ContentSlide', display_mode = 'fullscreen' } = req.body;

  if (!manuscript?.trim()) return res.status(400).json({ error: 'manuscript is required' });

  // Parse [S1], [S2]… — split produces: [before, num, text, num, text, …]
  const blocks = manuscript.split(/\[S(\d+)\]/);
  const parsed = [];
  for (let i = 1; i < blocks.length; i += 2) {
    parsed.push({
      num:           parseInt(blocks[i], 10),
      gnommoSlideId: `S${blocks[i]}`,
      notes:         blocks[i + 1]?.trim() ?? '',
    });
  }
  if (parsed.length === 0) return res.status(400).json({ error: 'No [S1]/[S2]… markers found' });
  parsed.sort((a, b) => a.num - b.num);

  const client = await pool.connect();
  try {
    await client.query('BEGIN');
    for (const { gnommoSlideId, notes, num } of parsed) {
      await client.query(
        `INSERT INTO slides
           (video_id, gnommo_slide_id, slide_order, display_mode,
            presenter_notes, component_key, props)
         VALUES ($1, $2, $3, $4, $5, $6, '{}')
         ON CONFLICT (video_id, gnommo_slide_id) DO UPDATE SET
           presenter_notes = EXCLUDED.presenter_notes,
           updated_at      = NOW()`,
        [id, gnommoSlideId, num, display_mode, notes, component_key]
      );
    }
    await client.query('COMMIT');

    const allSlides = await pool.query(
      'SELECT * FROM slides WHERE video_id = $1 ORDER BY slide_order', [id]
    );
    res.json({ slides: allSlides.rows, imported: parsed.length });
  } catch (err) {
    await client.query('ROLLBACK');
    console.error('Import manuscript error:', err);
    res.status(500).json({ error: 'Failed to import manuscript' });
  } finally {
    client.release();
  }
});

// DELETE /api/videos/:id/narration-segments/:segmentId
router.delete('/:id/narration-segments/:segmentId', async (req, res) => {
  const pool = req.app.get('pool');
  const { id, segmentId } = req.params;
  try {
    const result = await pool.query(
      'DELETE FROM narration_segments WHERE id = $1 AND project_id = $2 RETURNING id',
      [segmentId, id]
    );
    if (result.rows.length === 0) return res.status(404).json({ error: 'Segment not found' });
    res.json({ success: true });
  } catch (err) {
    console.error('Delete narration segment error:', err);
    res.status(500).json({ error: 'Failed to delete narration segment' });
  }
});

// GET /api/videos/:id/export — GnommoPlayer LectureDefinition
router.get('/:id/export', async (req, res) => {
  const pool = req.app.get('pool');
  const { id } = req.params;
  try {
    const videoResult = await pool.query('SELECT * FROM videos WHERE id = $1', [id]);
    if (videoResult.rows.length === 0) {
      return res.status(404).json({ error: 'Video not found' });
    }
    const video = videoResult.rows[0];

    const slidesResult = await pool.query(
      'SELECT * FROM slides WHERE video_id = $1 ORDER BY slide_order',
      [id]
    );

    // Prefer 'final' segments for the player; fall back to 'raw' if none exist.
    const segmentsResult = await pool.query(
      `SELECT ns.*,
              COALESCE(sa_id.duration_seconds, sa_key.duration_seconds) AS asset_duration
       FROM narration_segments ns
       LEFT JOIN shared_assets sa_id  ON ns.asset_id IS NOT NULL AND sa_id.id = ns.asset_id
       LEFT JOIN shared_assets sa_key ON ns.asset_id IS NULL     AND sa_key.minio_object_key = ns.minio_object_key
       WHERE ns.project_id = $1
         AND ns.segment_type = (
           SELECT CASE WHEN EXISTS(
             SELECT 1 FROM narration_segments WHERE project_id = $1 AND segment_type = 'final'
           ) THEN 'final' ELSE 'raw' END
         )
       ORDER BY ns.sort_order`,
      [id]
    );

    // Build segments with stable ids and cumulative start times
    let cursor = 0;
    const segments = segmentsResult.rows.map((seg) => {
      const dur = parseFloat(seg.take_seconds ?? seg.asset_duration ?? 0);
      const entry = {
        id: seg.segment_key,
        src: seg.minio_object_key ? publicUrl(seg.minio_object_key) : '',
        startTimeSec: cursor,
        durationSec: dur,
      };
      cursor += dur;
      return entry;
    });
    const totalDur = cursor || parseFloat(video.duration_seconds ?? 0);

    // Batch-fetch all candidates for this video's slides (graceful if table not yet migrated)
    const candidatesBySlideId = new Map();
    try {
      const candidatesResult = await pool.query(
        `SELECT sc.* FROM slide_candidates sc
         JOIN slides s ON s.id = sc.slide_id
         WHERE s.video_id = $1
         ORDER BY sc.slide_id, sc.score DESC`,
        [id]
      );
      for (const c of candidatesResult.rows) {
        const key = String(c.slide_id);
        if (!candidatesBySlideId.has(key)) candidatesBySlideId.set(key, []);
        candidatesBySlideId.get(key).push(c);
      }
    } catch {
      // Table doesn't exist yet — proceed with no candidates
    }

    // Build slides with stable ids and timing (evenly distribute if not set).
    // When totalDur = 0 (no narration yet), use 1 second per slide so each slide
    // gets a distinct, non-overlapping window — otherwise the player cannot
    // resolve which slide to show and always lands on slide 0 or no slide.
    const slideCount = slidesResult.rows.length;
    const effectiveDur = totalDur > 0 ? totalDur : slideCount;
    const rows = slidesResult.rows;
    const slides = rows.map((s, i) => {
      const slideId = s.gnommo_slide_id || `slide-${i + 1}`;
      const start = s.start_time_sec !== null
        ? parseFloat(s.start_time_sec)
        : slideCount > 0 ? (effectiveDur / slideCount) * i : 0;

      let end;
      if (s.end_time_sec !== null) {
        end = parseFloat(s.end_time_sec);
      } else {
        // Prefer the next slide's explicit start time as this slide's end,
        // so manually-set start times don't produce end < start.
        const nextRow = rows[i + 1];
        if (nextRow && nextRow.start_time_sec !== null) {
          end = parseFloat(nextRow.start_time_sec);
        } else {
          end = slideCount > 0 ? (effectiveDur / slideCount) * (i + 1) : effectiveDur;
        }
      }

      let glitchSlides;
      if (s.component_key) {
        const props = s.props || {};
        const glitchSlide = {
          id: `${slideId}:default`,
          score: 1,
          componentKey: s.component_key,
          props,
        };
        // latexString is a top-level glitch-slide field, but the editor stores it
        // inside props for convenience. Extract it so the player receives it correctly.
        if (props.latexString !== undefined) {
          glitchSlide.latexString = props.latexString;
        }
        const candidatesForSlide = candidatesBySlideId.get(String(s.id)) || [];
        glitchSlides = [
          glitchSlide,
          ...candidatesForSlide.map(c => {
            const cProps = c.props || {};
            const entry = {
              id: `${slideId}:${c.proposed_by}`,
              score: parseFloat(c.score),
              componentKey: c.component_key,
              props: cProps,
            };
            if (cProps.latexString !== undefined) entry.latexString = cProps.latexString;
            return entry;
          }),
        ];
      } else {
        glitchSlides = [];
      }

      return {
        id: slideId,
        startTimeSec: start,
        endTimeSec: end,
        displayMode: s.display_mode || 'square',
        glitchSlides,
      };
    });

    res.json({
      id: video.gnommo_id || String(video.id),
      title: video.title,
      version: '1.0',
      durationSec: effectiveDur,
      segments,
      slides,
    });
  } catch (err) {
    console.error('Export error:', err);
    res.status(500).json({ error: 'Failed to export video' });
  }
});

export default router;
