import express from 'express';
import http from 'http';
import pool from '../db/pool.js';
import { requireAuth } from '../middleware/auth.js';
import { v4 as uuidv4 } from 'uuid';

const router = express.Router();

router.use(requireAuth);

// Docker API helper function
function dockerApi(method, path, body = null) {
  return new Promise((resolve, reject) => {
    const options = {
      socketPath: '/var/run/docker.sock',
      path: `/v1.43${path}`,
      method,
      headers: { 'Content-Type': 'application/json' },
    };
    const req = http.request(options, (res) => {
      let data = '';
      res.on('data', chunk => data += chunk);
      res.on('end', () => {
        try {
          resolve({ status: res.statusCode, data: data ? JSON.parse(data) : {} });
        } catch {
          resolve({ status: res.statusCode, data });
        }
      });
    });
    req.on('error', reject);
    if (body) req.write(JSON.stringify(body));
    req.end();
  });
}

// Helper function to generate clip name with timestamp
function generateClipName(recorderName) {
  const now = new Date();
  const year = now.getFullYear();
  const month = String(now.getMonth() + 1).padStart(2, '0');
  const day = String(now.getDate()).padStart(2, '0');
  const hours = String(now.getHours()).padStart(2, '0');
  const minutes = String(now.getMinutes()).padStart(2, '0');
  const seconds = String(now.getSeconds()).padStart(2, '0');
  return `${recorderName}_${year}${month}${day}_${hours}${minutes}${seconds}`;
}

/**
 * Build Docker PortBindings and ExposedPorts for listener-mode recorders.
 * Returns { portBindings, exposedPorts } — both empty objects for non-listener sources.
 */
function buildPortConfig(sourceType, sourceConfig) {
  const portBindings = {};
  const exposedPorts = {};

  if (sourceConfig && sourceConfig.mode === 'listener') {
    if (sourceType === 'srt') {
      const port = String(sourceConfig.listen_port || 9000);
      const proto = `${port}/udp`;
      portBindings[proto] = [{ HostPort: port }];
      exposedPorts[proto] = {};
    } else if (sourceType === 'rtmp') {
      const port = String(sourceConfig.listen_port || 1935);
      const proto = `${port}/tcp`;
      portBindings[proto] = [{ HostPort: port }];
      exposedPorts[proto] = {};
    }
  }

  return { portBindings, exposedPorts };
}

// GET / - List all recorders
router.get('/', async (req, res, next) => {
  try {
    const result = await pool.query(
      'SELECT * FROM recorders ORDER BY created_at DESC'
    );
    // Annotate recording rows with the container's actual StartedAt + the
    // pre-created live asset id so the UI can keep a stable elapsed timer
    // and embed an HLS preview.
    const rows = await Promise.all(result.rows.map(async (r) => {
      if (r.status === 'recording' && r.container_id) {
        try {
          const insp = await dockerApi('GET', `/containers/${r.container_id}/json`);
          if (insp.status === 200 && insp.data && insp.data.State) {
            r.started_at = insp.data.State.StartedAt;
          }
        } catch (_) { /* leave started_at undefined */ }
        try {
          const live = await pool.query(
            `SELECT id FROM assets WHERE project_id = $1 AND display_name = $2 AND status = 'live' ORDER BY created_at DESC LIMIT 1`,
            [r.project_id, r.current_session_id]
          );
          if (live.rows.length > 0) r.live_asset_id = live.rows[0].id;
        } catch (_) { /* skip */ }
      }
      return r;
    }));
    res.json(rows);

  } catch (err) {
    next(err);
  }
});

// POST / - Create a new recorder
router.post('/', async (req, res, next) => {
  try {
    const {
      name,
      source_type,
      source_config,
      recording_codec,
      recording_resolution,
      proxy_enabled,
      proxy_codec,
      proxy_resolution,
      project_id,
    } = req.body;

    if (!name || !source_type) {
      return res
        .status(400)
        .json({ error: 'Name and source_type are required' });
    }

    const id = uuidv4();

    const result = await pool.query(
      `INSERT INTO recorders (
        id,
        name,
        source_type,
        source_config,
        recording_codec,
        recording_resolution,
        proxy_enabled,
        proxy_codec,
        proxy_resolution,
        project_id,
        status,
        created_at,
        updated_at
      )
      VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, NOW(), NOW())
      RETURNING *`,
      [
        id,
        name,
        source_type,
        source_config || {},
        recording_codec || 'prores_hq',
        recording_resolution || 'native',
        proxy_enabled !== false,
        proxy_codec || 'libx264',
        proxy_resolution || '1920x1080',
        project_id || null,
        'stopped',
      ]
    );

    res.status(201).json(result.rows[0]);
  } catch (err) {
    next(err);
  }
});

// GET /:id - Get single recorder
router.get('/:id', async (req, res, next) => {
  try {
    const { id } = req.params;

    const result = await pool.query(
      'SELECT * FROM recorders WHERE id = $1',
      [id]
    );

    if (result.rows.length === 0) {
      return res.status(404).json({ error: 'Recorder not found' });
    }

    res.json(result.rows[0]);
  } catch (err) {
    next(err);
  }
});

// PATCH /:id - Edit recorder settings
// Blocked while recorder is actively recording to prevent config drift.
router.patch('/:id', async (req, res, next) => {
  try {
    const { id } = req.params;

    const recorderResult = await pool.query(
      'SELECT * FROM recorders WHERE id = $1',
      [id]
    );
    if (recorderResult.rows.length === 0) {
      return res.status(404).json({ error: 'Recorder not found' });
    }

    const recorder = recorderResult.rows[0];
    if (recorder.status === 'recording') {
      return res.status(409).json({ error: 'Cannot edit a recorder while it is recording — stop it first' });
    }

    const {
      name,
      source_type,
      source_config,
      recording_codec,
      recording_resolution,
      proxy_enabled,
      proxy_codec,
      proxy_resolution,
      project_id,
    } = req.body;

    const updates = [];
    const params  = [];
    let   n       = 1;

    if (name                !== undefined) { updates.push(`name = $${n++}`);                  params.push(name); }
    if (source_type         !== undefined) { updates.push(`source_type = $${n++}`);           params.push(source_type); }
    if (source_config       !== undefined) { updates.push(`source_config = $${n++}`);         params.push(source_config); }
    if (recording_codec     !== undefined) { updates.push(`recording_codec = $${n++}`);       params.push(recording_codec); }
    if (recording_resolution !== undefined){ updates.push(`recording_resolution = $${n++}`);  params.push(recording_resolution); }
    if (proxy_enabled       !== undefined) { updates.push(`proxy_enabled = $${n++}`);         params.push(proxy_enabled); }
    if (proxy_codec         !== undefined) { updates.push(`proxy_codec = $${n++}`);           params.push(proxy_codec); }
    if (proxy_resolution    !== undefined) { updates.push(`proxy_resolution = $${n++}`);      params.push(proxy_resolution); }
    if (project_id          !== undefined) { updates.push(`project_id = $${n++}`);            params.push(project_id || null); }

    if (updates.length === 0) {
      return res.status(400).json({ error: 'No fields to update' });
    }

    updates.push(`updated_at = NOW()`);
    params.push(id);

    const result = await pool.query(
      `UPDATE recorders SET ${updates.join(', ')} WHERE id = $${n} RETURNING *`,
      params
    );

    res.json(result.rows[0]);
  } catch (err) {
    next(err);
  }
});

// POST /:id/start - Start recording
router.post('/:id/start', async (req, res, next) => {
  try {
    const { id } = req.params;

    // Get recorder config from DB
    const recorderResult = await pool.query(
      'SELECT * FROM recorders WHERE id = $1',
      [id]
    );

    if (recorderResult.rows.length === 0) {
      return res.status(404).json({ error: 'Recorder not found' });
    }

    const recorder = recorderResult.rows[0];

    if (recorder.status === 'recording') {
      return res.status(400).json({ error: 'Recorder is already recording' });
    }

    // Get S3 config from environment
    const s3Endpoint = process.env.S3_ENDPOINT;
    const s3Bucket = process.env.S3_BUCKET;
    const s3AccessKey = process.env.S3_ACCESS_KEY;
    const s3SecretKey = process.env.S3_SECRET_KEY;
    const mamApiUrl = process.env.MAM_API_URL || 'http://mam-api:3000';
    const dockerNetwork = process.env.DOCKER_NETWORK || 'wild-dragon_wild-dragon';

    // Generate clip name with timestamp
    const clipName = generateClipName(recorder.name);

    // live-asset: create the asset row right now (status='live') so the library
    // shows the recording while it is happening. The capture container will
    // tee an HLS stream into /live/<assetId>/.
    const assetIdLive = uuidv4();
    try {
      await pool.query(
        `INSERT INTO assets (
          id, project_id, bin_id, filename, display_name, status, media_type,
          original_s3_key, created_at, updated_at
        ) VALUES ($1, $2, NULL, $3, $3, 'live', 'video', $4, NOW(), NOW())`,
        [assetIdLive, recorder.project_id, clipName, `projects/${recorder.project_id}/masters/${clipName}.mov`]
      );
    } catch (e) {
      console.warn('[recorders] could not pre-create live asset:', e.message);
    }

    // Determine source config and whether this is a listener-mode recorder
    const sourceConfig = recorder.source_config || {};
    const isListener = sourceConfig.mode === 'listener';
    const sourceType = recorder.source_type;

    // Build port bindings for listener-mode SRT/RTMP containers
    const { portBindings, exposedPorts } = buildPortConfig(sourceType, sourceConfig);

    // Build container environment — pass all source params so the capture
    // service can auto-start recording on container startup
    const env = [
      `S3_ENDPOINT=${s3Endpoint}`,
      `S3_BUCKET=${s3Bucket}`,
      `S3_ACCESS_KEY=${s3AccessKey}`,
      `S3_SECRET_KEY=${s3SecretKey}`,
      `S3_REGION=${process.env.S3_REGION || 'us-east-1'}`,
      `MAM_API_URL=${mamApiUrl}`,
      `RECORDER_ID=${id}`,
      `SOURCE_TYPE=${sourceType}`,
      `SOURCE_CONFIG=${JSON.stringify(sourceConfig)}`,
      `RECORDING_CODEC=${recorder.recording_codec}`,
      `RECORDING_RESOLUTION=${recorder.recording_resolution}`,
      `PROXY_ENABLED=${recorder.proxy_enabled}`,
      `PROXY_CODEC=${recorder.proxy_codec}`,
      `PROXY_RESOLUTION=${recorder.proxy_resolution}`,
      `PROJECT_ID=${recorder.project_id}`,
      `CLIP_NAME=${clipName}`,
      `ASSET_ID=${assetIdLive}`,
    ];

    // Add source-specific env vars for SRT/RTMP
    if (sourceType === 'srt' || sourceType === 'rtmp') {
      env.push(`LISTEN=${isListener ? '1' : '0'}`);
      if (isListener) {
        env.push(`LISTEN_PORT=${sourceConfig.listen_port || (sourceType === 'srt' ? 9000 : 1935)}`);
        if (sourceType === 'rtmp' && sourceConfig.stream_key) {
          env.push(`STREAM_KEY=${sourceConfig.stream_key}`);
        }
      } else if (sourceConfig.url) {
        env.push(`SOURCE_URL=${sourceConfig.url}`);
      }
    }

    // Build container config
    // Stable network alias so mam-api can fetch live capture status by id
    const alias = `recorder-${id}`;
    const containerConfig = {
      Image: 'wild-dragon-capture:latest',
      Env: env,
      ExposedPorts: Object.keys(exposedPorts).length > 0 ? exposedPorts : undefined,
      HostConfig: {
        Privileged: true,
        NetworkMode: dockerNetwork,
        PortBindings: Object.keys(portBindings).length > 0 ? portBindings : undefined,
        Binds: ['/mnt/NVME/MAM/wild-dragon-live:/live'],
      },
      NetworkingConfig: {
        EndpointsConfig: {
          [dockerNetwork]: { Aliases: [alias] },
        },
      },
      Hostname: alias,
    };

    // Create container
    const createRes = await dockerApi('POST', '/containers/create', containerConfig);

    if (createRes.status !== 201) {
      return res.status(500).json({
        error: 'Failed to create container',
        details: createRes.data,
      });
    }

    const containerId = createRes.data.Id;

    // Start container
    const startRes = await dockerApi('POST', `/containers/${containerId}/start`);

    if (startRes.status !== 204) {
      return res.status(500).json({
        error: 'Failed to start container',
        details: startRes.data,
      });
    }

    // Update recorder in DB
    const updateResult = await pool.query(
      `UPDATE recorders
       SET container_id = $1, status = $2, current_session_id = $3, updated_at = NOW()
       WHERE id = $4
       RETURNING *`,
      [containerId, 'recording', clipName, id]
    );

    res.json(updateResult.rows[0]);
  } catch (err) {
    next(err);
  }
});

// POST /:id/stop - Stop recording
router.post('/:id/stop', async (req, res, next) => {
  try {
    const { id } = req.params;

    // Get recorder from DB
    const recorderResult = await pool.query(
      'SELECT * FROM recorders WHERE id = $1',
      [id]
    );

    if (recorderResult.rows.length === 0) {
      return res.status(404).json({ error: 'Recorder not found' });
    }

    const recorder = recorderResult.rows[0];

    if (!recorder.container_id) {
      return res.status(400).json({ error: 'No container running' });
    }

    // Stop container
    const stopRes = await dockerApi(
      'POST',
      `/containers/${recorder.container_id}/stop`
    );

    // 204 = stopped, 304 = already stopped — both are acceptable
    if (stopRes.status !== 204 && stopRes.status !== 304) {
      return res.status(500).json({
        error: 'Failed to stop container',
        details: stopRes.data,
      });
    }

    // Remove container — 204 = removed, 404 = already gone (both acceptable)
    const removeRes = await dockerApi(
      'DELETE',
      `/containers/${recorder.container_id}`
    );

    if (removeRes.status !== 204 && removeRes.status !== 404) {
      return res.status(500).json({
        error: 'Failed to remove container',
        details: removeRes.data,
      });
    }

    // Update recorder in DB
    const updateResult = await pool.query(
      `UPDATE recorders
       SET container_id = NULL, status = $1, updated_at = NOW()
       WHERE id = $2
       RETURNING *`,
      ['stopped', id]
    );

    res.json(updateResult.rows[0]);
  } catch (err) {
    next(err);
  }
});

// GET /:id/status - Get live status
router.get('/:id/status', async (req, res, next) => {
  try {
    const { id } = req.params;

    // Get recorder from DB
    const recorderResult = await pool.query(
      'SELECT * FROM recorders WHERE id = $1',
      [id]
    );

    if (recorderResult.rows.length === 0) {
      return res.status(404).json({ error: 'Recorder not found' });
    }

    const recorder = recorderResult.rows[0];

    if (!recorder.container_id) {
      return res.json({
        status: recorder.status,
        duration: 0,
        containerId: null,
      });
    }

    // Query Docker API for container status
    const inspectRes = await dockerApi(
      'GET',
      `/containers/${recorder.container_id}/json`
    );

    if (inspectRes.status !== 200) {
      return res.json({
        status: 'unknown',
        duration: 0,
        containerId: recorder.container_id,
      });
    }

    const container = inspectRes.data;
    const startedAt = new Date(container.State.StartedAt).getTime();
    const now = Date.now();
    const duration = Math.floor((now - startedAt) / 1000);

    // Try to fetch live signal from the recorder's capture sidecar via network alias
    let signal = container.State.Running ? 'receiving' : 'stopped';
    let signalKnown = false;
    let live = null;
    try {
      const captureRes = await fetch(`http://recorder-${id}:3001/capture/status`, { signal: AbortSignal.timeout(2000) });
      if (captureRes.ok) {
        live = await captureRes.json();
        if (live && live.signal) { signal = live.signal; signalKnown = true; }
      }
    } catch (_) {
      // Container may not be ready yet, or alias hasn't propagated. Leave signal as default.
    }

    res.json({
      status: container.State.Running ? 'recording' : 'stopped',
      duration,
      containerId: recorder.container_id,
      signal,
      signalKnown,
      framesReceived: live ? live.framesReceived : null,
      currentFps: live ? live.currentFps : null,
      lastFrameAt: live ? live.lastFrameAt : null,
      lastError: live ? live.lastError : null,
    });
  } catch (err) {
    next(err);
  }
});

// DELETE /:id - Delete recorder
router.delete('/:id', async (req, res, next) => {
  try {
    const { id } = req.params;

    // Get recorder from DB
    const recorderResult = await pool.query(
      'SELECT * FROM recorders WHERE id = $1',
      [id]
    );

    if (recorderResult.rows.length === 0) {
      return res.status(404).json({ error: 'Recorder not found' });
    }

    const recorder = recorderResult.rows[0];

    // If recording, stop the container first
    if (recorder.container_id) {
      try {
        await dockerApi('POST', `/containers/${recorder.container_id}/stop`);
        await dockerApi('DELETE', `/containers/${recorder.container_id}`);
      } catch (err) {
        console.error('Error stopping container during delete:', err);
      }
    }

    // Delete from DB
    const deleteResult = await pool.query(
      'DELETE FROM recorders WHERE id = $1 RETURNING *',
      [id]
    );

    res.json({ message: 'Recorder deleted', recorder: deleteResult.rows[0] });
  } catch (err) {
    next(err);
  }
});

router.post('/probe', async (req, res, next) => {
  try {
    const r = await fetch('http://capture:3001/capture/probe', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(req.body || {}),
      signal: AbortSignal.timeout(15000),
    });
    const data = await r.json().catch(() => ({}));
    res.status(r.status).json(data);
  } catch (err) {
    next(err);
  }
});

export default router;
