const fs = require('fs');
const fsp = fs.promises;
const http = require('http');
const path = require('path');
const { spawn } = require('child_process');

const ROOT = process.cwd();
const RENDERER_PATH = path.join(ROOT, 'scripts', 'social-video-renderer.html');
const OUTPUT_DIR = path.join(ROOT, 'generated', 'social-videos');
const EDGE_DEBUG_PORT = Number(process.env.EDGE_DEBUG_PORT || '9222');
const videos = [
  {
    outputName: 'greenlens-yellow-leaves.webm',
    accentA: '#143625',
    accentB: '#3f855f',
    scenes: [
      {
        image: '/greenlns-landing/public/unhealthy-plant.png',
        badge: 'Plant Rescue',
        title: 'Why Are My Plant Leaves Turning Yellow?',
        subtitle: 'Start with a scan instead of another random guess.',
        cta: 'Save this for your next plant emergency',
        durationMs: 2600,
      },
      {
        image: '/greenlns-landing/public/scan-feature.png',
        badge: 'GreenLens',
        title: 'Scan The Plant In Seconds',
        subtitle: 'Get the plant name and a faster clue about what is going wrong.',
        cta: 'Open the app and scan',
        durationMs: 2600,
      },
      {
        image: '/greenlns-landing/public/ai-analysis.png',
        badge: 'Care Help',
        title: 'See The Likely Care Issue',
        subtitle: 'Less guessing. Faster fixes. Better plant care.',
        cta: 'Plant ID plus care guidance',
        durationMs: 2600,
      },
      {
        image: '/greenlns-landing/public/plant-collection.png',
        badge: 'Result',
        title: 'Give Your Plant A Better Recovery Plan',
        subtitle: 'GreenLens helps you move from panic to action.',
        cta: 'GreenLens',
        durationMs: 2400,
      },
    ],
  },
  {
    outputName: 'greenlens-mystery-plant.webm',
    accentA: '#18392b',
    accentB: '#5ba174',
    scenes: [
      {
        image: '/greenlns-landing/public/hero-plant.png',
        badge: 'Mystery Plant',
        title: 'I Had This Plant For Months',
        subtitle: 'And I still did not know what it was.',
        cta: 'No more mystery plants',
        durationMs: 2600,
      },
      {
        image: '/greenlns-landing/public/scan-feature.png',
        badge: 'Scan',
        title: 'Point. Scan. Identify.',
        subtitle: 'Get the plant name in a few seconds with GreenLens.',
        cta: 'Tap to scan',
        durationMs: 2500,
      },
      {
        image: '/greenlns-landing/public/ai-analysis.png',
        badge: 'Know More',
        title: 'See The Species And Care Basics',
        subtitle: 'That means less overwatering and fewer avoidable mistakes.',
        cta: 'Plant ID and care basics',
        durationMs: 2600,
      },
      {
        image: '/greenlns-landing/public/track-feature.png',
        badge: 'Next Step',
        title: 'Now You Can Actually Care For It Right',
        subtitle: 'Knowing the name changes everything.',
        cta: 'Comment for more plant scans',
        durationMs: 2300,
      },
    ],
  },
  {
    outputName: 'greenlens-plant-routine.webm',
    accentA: '#102d21',
    accentB: '#4f916d',
    scenes: [
      {
        image: '/greenlns-landing/public/plant-collection.png',
        badge: 'POV',
        title: 'You Love Plants But Forget Their Care Routine',
        subtitle: 'Too many plants. Too many watering schedules.',
        cta: 'Plant parent problems',
        durationMs: 2700,
      },
      {
        image: '/greenlns-landing/public/track-feature.png',
        badge: 'Tracking',
        title: 'Keep Care Details In One Place',
        subtitle: 'Watering, light needs, and reminders without mental overload.',
        cta: 'Track care with GreenLens',
        durationMs: 2600,
      },
      {
        image: '/greenlns-landing/public/scan-feature.png',
        badge: 'Simple Start',
        title: 'Scan First Then Build The Routine',
        subtitle: 'A better system starts with a better plant ID.',
        cta: 'Scan and track',
        durationMs: 2500,
      },
      {
        image: '/greenlns-landing/public/hero-plant.png',
        badge: 'Calmer Care',
        title: 'Healthy Plants Need Fewer Guesswork Decisions',
        subtitle: 'GreenLens makes plant care easier to manage.',
        cta: 'Follow for more plant shortcuts',
        durationMs: 2400,
      },
    ],
  },
  {
    outputName: 'greenlens-sick-plant-rescue.webm',
    accentA: '#1e2c18',
    accentB: '#63844d',
    scenes: [
      {
        image: '/greenlns-landing/public/unhealthy-plant.png',
        badge: 'Rescue',
        title: 'This Plant Looked Like It Was Dying',
        subtitle: 'The first move was not more water. It was a diagnosis.',
        cta: 'Stop guessing first',
        durationMs: 2700,
      },
      {
        image: '/greenlns-landing/public/scan-feature.png',
        badge: 'Step 1',
        title: 'Scan The Problem Plant',
        subtitle: 'Use GreenLens to identify what you are actually dealing with.',
        cta: 'Start with a scan',
        durationMs: 2500,
      },
      {
        image: '/greenlns-landing/public/ai-analysis.png',
        badge: 'Step 2',
        title: 'Get Care Guidance Faster',
        subtitle: 'Know whether the issue points to watering, light, or something else.',
        cta: 'Faster plant triage',
        durationMs: 2600,
      },
      {
        image: '/greenlns-landing/public/plant-collection.png',
        badge: 'Step 3',
        title: 'Turn Panic Into A Care Plan',
        subtitle: 'A stressed plant needs informed action, not random fixes.',
        cta: 'Send this to a plant parent',
        durationMs: 2400,
      },
    ],
  },
  {
    outputName: 'greenlens-fast-demo.webm',
    accentA: '#183724',
    accentB: '#56a074',
    scenes: [
      {
        image: '/greenlns-landing/public/scan-feature.png',
        badge: 'Fast Demo',
        title: 'Scan Any Plant In Seconds',
        subtitle: 'GreenLens turns a quick camera scan into plant insight.',
        cta: 'Simple plant ID',
        durationMs: 2500,
      },
      {
        image: '/greenlns-landing/public/ai-analysis.png',
        badge: 'AI',
        title: 'See The Name And Care Guidance',
        subtitle: 'Get plant details without digging through search results.',
        cta: 'Plant ID plus help',
        durationMs: 2600,
      },
      {
        image: '/greenlns-landing/public/track-feature.png',
        badge: 'Routine',
        title: 'Track Care After The Scan',
        subtitle: 'Keep watering and plant health details organized.',
        cta: 'Track what matters',
        durationMs: 2400,
      },
      {
        image: '/greenlns-landing/public/hero-plant.png',
        badge: 'GreenLens',
        title: 'Plant ID And Care Help In One App',
        subtitle: 'Use it on your next mystery plant.',
        cta: 'GreenLens',
        durationMs: 2400,
      },
    ],
  },
];

async function ensureDir(dir) {
  await fsp.mkdir(dir, { recursive: true });
}

function contentType(filePath) {
  const ext = path.extname(filePath).toLowerCase();
  if (ext === '.html') return 'text/html; charset=utf-8';
  if (ext === '.png') return 'image/png';
  if (ext === '.jpg' || ext === '.jpeg') return 'image/jpeg';
  if (ext === '.webm') return 'video/webm';
  if (ext === '.mp4') return 'video/mp4';
  if (ext === '.svg') return 'image/svg+xml';
  return 'application/octet-stream';
}

async function startServer() {
  await ensureDir(OUTPUT_DIR);
  const server = http.createServer(async (req, res) => {
    try {
      const url = new URL(req.url, 'http://127.0.0.1');
      if (req.method === 'POST' && url.pathname === '/upload') {
        const name = path.basename(url.searchParams.get('name') || 'output.webm');
        const target = path.join(OUTPUT_DIR, name);
        const chunks = [];
        req.on('data', (chunk) => chunks.push(chunk));
        req.on('end', async () => {
          await fsp.writeFile(target, Buffer.concat(chunks));
          res.writeHead(200, { 'Content-Type': 'text/plain; charset=utf-8' });
          res.end(target);
        });
        return;
      }

      let filePath;
      if (url.pathname === '/renderer') {
        filePath = RENDERER_PATH;
      } else {
        filePath = path.join(ROOT, decodeURIComponent(url.pathname.replace(/^\/+/, '')));
      }

      const normalized = path.normalize(filePath);
      if (!normalized.startsWith(ROOT)) {
        res.writeHead(403);
        res.end('Forbidden');
        return;
      }

      const data = await fsp.readFile(normalized);
      res.writeHead(200, { 'Content-Type': contentType(normalized) });
      res.end(data);
    } catch (error) {
      res.writeHead(404, { 'Content-Type': 'text/plain; charset=utf-8' });
      res.end(String(error));
    }
  });

  await new Promise((resolve) => server.listen(0, '127.0.0.1', resolve));
  const address = server.address();
  return { server, port: address.port };
}

async function fetchJson(url, retries = 60) {
  let lastError;
  for (let attempt = 0; attempt < retries; attempt += 1) {
    try {
      const response = await fetch(url);
      if (!response.ok) {
        throw new Error(`HTTP ${response.status}`);
      }
      return response.json();
    } catch (error) {
      lastError = error;
      await new Promise((resolve) => setTimeout(resolve, 250));
    }
  }
  throw lastError;
}

async function waitForPageReady(sessionSend, retries = 80) {
  for (let attempt = 0; attempt < retries; attempt += 1) {
    const result = await sessionSend('Runtime.evaluate', {
      expression: 'document.readyState',
      returnByValue: true,
    }).catch(() => null);

    if (result && result.result && result.result.value === 'complete') {
      return;
    }

    await new Promise((resolve) => setTimeout(resolve, 250));
  }

  throw new Error('Timed out waiting for renderer page readiness');
}

function runCommand(command, timeoutMs = 30000) {
  return new Promise((resolve, reject) => {
    const child = spawn(
      'C:\\WINDOWS\\System32\\WindowsPowerShell\\v1.0\\powershell.exe',
      ['-NoProfile', '-Command', command],
      { stdio: ['ignore', 'pipe', 'pipe'] }
    );

    let stdout = '';
    let stderr = '';
    const timeout = setTimeout(() => {
      child.kill();
      reject(new Error(`Command timed out: ${command}`));
    }, timeoutMs);

    child.stdout.on('data', (chunk) => {
      stdout += chunk.toString();
    });
    child.stderr.on('data', (chunk) => {
      stderr += chunk.toString();
    });

    child.on('exit', (code) => {
      clearTimeout(timeout);
      if (code === 0) {
        resolve(stdout.trim());
      } else {
        reject(new Error(stderr || stdout || `Command failed with code ${code}`));
      }
    });
  });
}

async function startEdge(profileDir) {
  const escapedProfile = profileDir.replace(/'/g, "''");
  const command = [
    "$p = Start-Process -FilePath 'C:\\Program Files (x86)\\Microsoft\\Edge\\Application\\msedge.exe'",
    `-ArgumentList @('--headless','--disable-gpu','--no-sandbox','--disable-crash-reporter','--disable-breakpad','--noerrdialogs','--autoplay-policy=no-user-gesture-required','--mute-audio','--hide-scrollbars','--remote-debugging-port=${EDGE_DEBUG_PORT}',`,
    `'--user-data-dir=${escapedProfile}','about:blank')`,
    '-PassThru;',
    'Write-Output $p.Id',
  ].join(' ');
  const pidText = await runCommand(command, 30000);
  return Number(pidText);
}

async function stopEdge(pid) {
  if (!pid) return;
  await runCommand(`Stop-Process -Id ${pid} -Force -ErrorAction SilentlyContinue`, 15000).catch(() => {});
}

class CDPClient {
  constructor(wsUrl) {
    this.ws = new WebSocket(wsUrl);
    this.id = 0;
    this.pending = new Map();
    this.events = [];
    this.waiters = [];
  }

  async connect() {
    await new Promise((resolve, reject) => {
      this.ws.addEventListener('open', resolve, { once: true });
      this.ws.addEventListener('error', reject, { once: true });
    });

    this.ws.addEventListener('message', (event) => {
      const message = JSON.parse(event.data);
      if (message.id) {
        const pending = this.pending.get(message.id);
        if (pending) {
          this.pending.delete(message.id);
          if (message.error) {
            pending.reject(new Error(message.error.message));
          } else {
            pending.resolve(message.result);
          }
        }
        return;
      }

      this.events.push(message);
      this.waiters = this.waiters.filter((waiter) => {
        if (waiter.predicate(message)) {
          waiter.resolve(message);
          return false;
        }
        return true;
      });
    });
  }

  send(method, params = {}, sessionId) {
    const id = ++this.id;
    const message = sessionId
      ? { id, method, params, sessionId }
      : { id, method, params };
    this.ws.send(JSON.stringify(message));
    return new Promise((resolve, reject) => {
      this.pending.set(id, { resolve, reject });
    });
  }

  waitFor(predicate, timeoutMs = 30000) {
    const existing = this.events.find(predicate);
    if (existing) {
      return Promise.resolve(existing);
    }
    return new Promise((resolve, reject) => {
      const timeout = setTimeout(() => {
        this.waiters = this.waiters.filter((waiter) => waiter.resolve !== resolve);
        reject(new Error('Timed out waiting for event'));
      }, timeoutMs);

      this.waiters.push({
        predicate,
        resolve: (message) => {
          clearTimeout(timeout);
          resolve(message);
        },
      });
    });
  }

  close() {
    this.ws.close();
  }
}

async function run() {
  await ensureDir(OUTPUT_DIR);
  const profileDir = path.join(ROOT, '.edge-profile-render');
  const { server, port } = await startServer();
  const useExternalEdge = process.env.EDGE_EXTERNAL === '1';
  let edgePid = null;

  try {
    console.log(`server:${port}`);
    console.log(`externalEdge:${useExternalEdge}`);
    if (!useExternalEdge) {
      edgePid = await startEdge(profileDir);
    }
    console.log('fetching-devtools-version');
    const version = await fetchJson(`http://127.0.0.1:${EDGE_DEBUG_PORT}/json/version`);
    console.log('devtools-version-ready');
    const client = new CDPClient(version.webSocketDebuggerUrl);
    await client.connect();
    console.log('cdp-connected');

    const { targetId } = await client.send('Target.createTarget', {
      url: 'about:blank',
    });
    console.log(`target:${targetId}`);
    const { sessionId } = await client.send('Target.attachToTarget', {
      targetId,
      flatten: true,
    });
    console.log(`session:${sessionId}`);

    const sessionSend = (method, params = {}) =>
      client.send(method, params, sessionId);

    await sessionSend('Page.enable');
    await sessionSend('Runtime.enable');
    await sessionSend('Page.navigate', {
      url: `http://127.0.0.1:${port}/renderer`,
    });
    await waitForPageReady(sessionSend);

    for (const video of videos) {
      const payload = {
        ...video,
        logo: '/assets/icon.png',
        fps: 30,
      };

      const expression = `window.renderVideo(${JSON.stringify(payload)})`;
      const result = await sessionSend('Runtime.evaluate', {
        expression,
        awaitPromise: true,
        returnByValue: true,
      });

      if (!result.result || !result.result.value) {
        throw new Error(`Renderer did not return a file path for ${video.outputName}`);
      }
    }

    client.close();
  } finally {
    if (!useExternalEdge) {
      await stopEdge(edgePid);
    }
    server.close();
  }
}

run().catch((error) => {
  console.error(error);
  process.exitCode = 1;
});
