import { Router } from 'express';
import { requireIngestKey } from '../middleware/auth.js';

const router = Router();

// Parse manuscript text into a map of slide_id -> presenter notes.
function parseManuscript(text) {
  const notes = {};
  if (!text) return notes;
  const blocks = text.split(/\[S(\d+)\]/);
  for (let i = 1; i < blocks.length; i += 2) {
    const slideNum = blocks[i];
    const content = blocks[i + 1]?.trim() ?? '';
    notes[`S${slideNum}`] = content;
  }
  return notes;
}

// Extract numeric sort key from "Segment001" -> 1
function segmentSortOrder(key) {
  const m = key.match(/(\d+)$/);
  return m ? parseInt(m[1], 10) : 0;
}

// POST /api/ingest
router.post('/', requireIngestKey, async (req, res) => {
  const pool = req.app.get('pool');
  const {
    project,
    slides,
    manuscript,
    narration,
    audio,
    videos: videoAssets,
    citations,
    sync,
  } = req.body;

  if (!project) return res.status(400).json({ error: 'project is required' });
  if (!project.id) return res.status(400).json({ error: 'project.id is required' });

  const presenterNotes = parseManuscript(manuscript);
  const client = await pool.connect();

  try {
    await client.query('BEGIN');

    // ── 1. Upsert video ────────────────────────────────────────────────────
    const res_v = await client.query(
      `INSERT INTO videos (
        gnommo_id, course_code, title, description, hook, status,
        resolution_width, resolution_height, fps,
        default_slide_type, background, platform_targets,
        duration_seconds, youtube_url,
        output_video, keynote_file, parent_gnommo_id, outro, shorts, manuscript_path,
        raw_project_json, updated_at
      ) VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14,$15,$16,$17,$18,$19,$20,$21,NOW())
      ON CONFLICT (gnommo_id) DO UPDATE SET
        course_code = EXCLUDED.course_code,
        title = EXCLUDED.title,
        description = EXCLUDED.description,
        hook = EXCLUDED.hook,
        status = EXCLUDED.status,
        resolution_width = EXCLUDED.resolution_width,
        resolution_height = EXCLUDED.resolution_height,
        fps = EXCLUDED.fps,
        default_slide_type = EXCLUDED.default_slide_type,
        background = EXCLUDED.background,
        platform_targets = EXCLUDED.platform_targets,
        duration_seconds = EXCLUDED.duration_seconds,
        youtube_url = EXCLUDED.youtube_url,
        output_video = EXCLUDED.output_video,
        keynote_file = EXCLUDED.keynote_file,
        parent_gnommo_id = EXCLUDED.parent_gnommo_id,
        outro = EXCLUDED.outro,
        shorts = EXCLUDED.shorts,
        manuscript_path = EXCLUDED.manuscript_path,
        raw_project_json = EXCLUDED.raw_project_json,
        updated_at = NOW()
      RETURNING id`,
      [
        project.id,
        project.coursecode ?? project.course_code ?? null,
        project.name ?? project.title ?? project.id,
        project.description ?? null,
        project.hook ?? null,
        project.status ?? null,
        project.resolution?.[0] ?? project.resolution_width ?? null,
        project.resolution?.[1] ?? project.resolution_height ?? null,
        project.fps ?? null,
        project.defaultSlideType ?? project.default_slide_type ?? null,
        project.background ?? null,
        project.platform_targets ?? null,
        project.duration_seconds ?? null,
        project.youtube_url ?? null,
        project.output_video ?? null,
        project.keynote_file ?? null,
        project.parent_project ?? null,
        project.outro ?? null,
        project.shorts ?? null,
        project.manuscript ?? null,
        JSON.stringify(project),
      ]
    );
    const videoId = res_v.rows[0].id;

    // ── 2. Upsert slides ───────────────────────────────────────────────────
    let slidesUpserted = 0;
    if (slides) {
      for (const [slideId, data] of Object.entries(slides)) {
        const orderMatch = slideId.match(/^S(\d+)$/i);
        const slideOrder = orderMatch ? parseInt(orderMatch[1], 10) : 0;
        await client.query(
          `INSERT INTO slides (
            video_id, gnommo_slide_id, slide_order,
            image_filename, display_mode, presenter_notes, updated_at
          ) VALUES ($1,$2,$3,$4,$5,$6,NOW())
          ON CONFLICT (video_id, gnommo_slide_id) DO UPDATE SET
            slide_order = EXCLUDED.slide_order,
            image_filename = EXCLUDED.image_filename,
            display_mode = EXCLUDED.display_mode,
            presenter_notes = EXCLUDED.presenter_notes,
            updated_at = NOW()`,
          [videoId, slideId, slideOrder, data.image ?? null, data.type ?? null,
           presenterNotes[slideId] ?? null]
        );
        slidesUpserted++;
      }
    }

    // ── 3. Upsert cutouts ──────────────────────────────────────────────────
    if (project.cutouts) {
      for (const [name, c] of Object.entries(project.cutouts)) {
        await client.query(
          `INSERT INTO cutouts (project_id, name, x, y, width, height, updated_at)
           VALUES ($1,$2,$3,$4,$5,$6,NOW())
           ON CONFLICT (project_id, name) DO UPDATE SET
             x = EXCLUDED.x, y = EXCLUDED.y,
             width = EXCLUDED.width, height = EXCLUDED.height,
             updated_at = NOW()`,
          [videoId, name, c.x ?? null, c.y ?? null, c.width ?? null, c.height ?? null]
        );
      }
    }

    // ── 4. Replace filter chains ───────────────────────────────────────────
    if (project.default_filters) {
      await client.query('DELETE FROM filter_chains WHERE project_id = $1', [videoId]);
      for (const [target, filters] of Object.entries(project.default_filters)) {
        for (let i = 0; i < filters.length; i++) {
          const f = filters[i];
          const { type: filterType, ...params } = f;
          await client.query(
            `INSERT INTO filter_chains (project_id, target, sort_order, filter_type, params, updated_at)
             VALUES ($1,$2,$3,$4,$5,NOW())`,
            [videoId, target, i, filterType, JSON.stringify(params)]
          );
        }
      }
    }

    // ── 5. Upsert video assets ─────────────────────────────────────────────
    if (videoAssets) {
      for (const [key, v] of Object.entries(videoAssets)) {
        await client.query(
          `INSERT INTO video_assets (
            project_id, asset_key, description, source_file, output_file,
            is_shared, cutout_name, duration_seconds, has_audio, volume,
            always_visible, pause_narration_seconds, skip_seconds, take_seconds,
            filters, raw_json, updated_at
          ) VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14,$15,$16,NOW())
          ON CONFLICT (project_id, asset_key) DO UPDATE SET
            description = EXCLUDED.description,
            source_file = EXCLUDED.source_file,
            output_file = EXCLUDED.output_file,
            is_shared = EXCLUDED.is_shared,
            cutout_name = EXCLUDED.cutout_name,
            duration_seconds = EXCLUDED.duration_seconds,
            has_audio = EXCLUDED.has_audio,
            volume = EXCLUDED.volume,
            always_visible = EXCLUDED.always_visible,
            pause_narration_seconds = EXCLUDED.pause_narration_seconds,
            skip_seconds = EXCLUDED.skip_seconds,
            take_seconds = EXCLUDED.take_seconds,
            filters = EXCLUDED.filters,
            raw_json = EXCLUDED.raw_json,
            updated_at = NOW()`,
          [
            videoId, key,
            v.description ?? null,
            v.source_file ?? null,
            v.output_file ?? null,
            v.is_shared ?? false,
            v.cutout ?? null,
            v.duration ?? null,
            v.has_audio ?? false,
            v.volume ?? 1.0,
            v.always_visible ?? false,
            v.pause_narration ?? null,
            v.skip ?? null,
            v.take ?? null,
            v.filter != null ? JSON.stringify(v.filter) : null,
            JSON.stringify(v),
          ]
        );
      }
    }

    // ── 6. Upsert narration segments ───────────────────────────────────────
    if (narration) {
      for (const [key, seg] of Object.entries(narration)) {
        await client.query(
          `INSERT INTO narration_segments (
            project_id, segment_key, source_file,
            use_audio_channels, defer_loudnorm,
            skip_seconds, take_seconds, sort_order, updated_at
          ) VALUES ($1,$2,$3,$4,$5,$6,$7,$8,NOW())
          ON CONFLICT (project_id, segment_key) DO UPDATE SET
            source_file = EXCLUDED.source_file,
            use_audio_channels = EXCLUDED.use_audio_channels,
            defer_loudnorm = EXCLUDED.defer_loudnorm,
            skip_seconds = EXCLUDED.skip_seconds,
            take_seconds = EXCLUDED.take_seconds,
            sort_order = EXCLUDED.sort_order,
            updated_at = NOW()`,
          [
            videoId, key,
            seg.source_file ?? null,
            seg.use_audio_channels ?? 'auto',
            seg.defer_loudnorm ?? false,
            seg.skip ?? null,
            seg.take ?? null,
            segmentSortOrder(key),
          ]
        );
      }
    }

    // ── 7. Upsert audio tracks ─────────────────────────────────────────────
    if (audio) {
      for (const [key, track] of Object.entries(audio)) {
        await client.query(
          `INSERT INTO audio_tracks (
            project_id, track_key, source_file,
            volume, loop, overlap, ignore_pauses, duration_seconds, updated_at
          ) VALUES ($1,$2,$3,$4,$5,$6,$7,$8,NOW())
          ON CONFLICT (project_id, track_key) DO UPDATE SET
            source_file = EXCLUDED.source_file,
            volume = EXCLUDED.volume,
            loop = EXCLUDED.loop,
            overlap = EXCLUDED.overlap,
            ignore_pauses = EXCLUDED.ignore_pauses,
            duration_seconds = EXCLUDED.duration_seconds,
            updated_at = NOW()`,
          [
            videoId, key,
            track.file ?? null,
            track.volume ?? 1.0,
            track.loop ?? false,
            track.overlap ?? null,
            track.ignore_pauses ?? false,
            track.duration ?? null,
          ]
        );
      }
    }

    // ── 8. Replace citations (no stable key — delete + re-insert) ──────────
    if (citations) {
      await client.query('DELETE FROM citations WHERE project_id = $1', [videoId]);
      for (let i = 0; i < citations.length; i++) {
        const c = citations[i];
        await client.query(
          `INSERT INTO citations (project_id, reference, context, sort_order)
           VALUES ($1,$2,$3,$4)`,
          [videoId, c.reference ?? '', c.context ?? null, i]
        );
      }
    }

    // ── 9. Upsert project sync metadata ───────────────────────────────────
    if (sync) {
      await client.query(
        `INSERT INTO project_sync (
          project_id, last_pulled_at, server_updated_at,
          last_pushed_at, last_handoff_at, video_version
        ) VALUES ($1,$2,$3,$4,$5,$6)
        ON CONFLICT (project_id) DO UPDATE SET
          last_pulled_at = EXCLUDED.last_pulled_at,
          server_updated_at = EXCLUDED.server_updated_at,
          last_pushed_at = EXCLUDED.last_pushed_at,
          last_handoff_at = EXCLUDED.last_handoff_at,
          video_version = EXCLUDED.video_version`,
        [
          videoId,
          sync.last_pulled_at ?? null,
          sync.server_updated_at ?? null,
          sync.last_pushed_at ?? null,
          sync.last_handoff_at ?? null,
          sync.video_version ?? 0,
        ]
      );
    }

    await client.query('COMMIT');

    res.json({
      success: true,
      video_id: videoId,
      slides_upserted: slidesUpserted,
    });

  } catch (err) {
    await client.query('ROLLBACK');
    console.error('Ingest error:', err);
    res.status(500).json({ error: 'Ingest failed', detail: err.message });
  } finally {
    client.release();
  }
});

export default router;
