const { app, BrowserWindow, screen, Tray, Menu, ipcMain, dialog } = require('electron');
const { spawn } = require('child_process');
const crypto = require('crypto');
const fs = require('fs');
const path = require('path');

const isDev = !app.isPackaged;
const APP_VERSION = '1.1';

// Lu directement dans le .env du projet (pas de build Vite pour le processus
// principal) : même variable que celle utilisée par l'overlay (Overlay.vue).
function loadBackendUrl() {
  try {
    const content = fs.readFileSync(path.join(__dirname, '.env'), 'utf-8');
    const match = content.match(/^VITE_BACKEND_URL=(.*)$/m);
    return match ? match[1].trim() : 'http://localhost:3000';
  } catch {
    return 'http://localhost:3000';
  }
}

const BACKEND_URL = loadBackendUrl();

const DEFAULT_SETTINGS = {
  volume: 1,
  anchor: 'center',
  sizeScale: 1,
};

const settingsPath = path.join(app.getPath('userData'), 'settings.json');

function loadSettings() {
  try {
    const raw = fs.readFileSync(settingsPath, 'utf-8');
    return { ...DEFAULT_SETTINGS, ...JSON.parse(raw) };
  } catch {
    return { ...DEFAULT_SETTINGS };
  }
}

function saveSettings(settings) {
  fs.writeFileSync(settingsPath, JSON.stringify(settings));
}

let settings = loadSettings();
let tray;
let overlayWindow;
let settingsWindow;

// null = actif, Infinity = arrêt complet (jusqu'à réactivation manuelle),
// sinon timestamp (ms) auquel le livechat se réactive tout seul.
let pauseUntil = null;
let pauseTimer = null;

const appIconPath = path.join(__dirname, 'assets', 'tray-icon.png');

function broadcastSettings() {
  for (const win of BrowserWindow.getAllWindows()) {
    win.webContents.send('settings:changed', settings);
  }
}

ipcMain.handle('settings:get', () => settings);
ipcMain.on('settings:set', (event, partial) => {
  settings = { ...settings, ...partial };
  saveSettings(settings);
  broadcastSettings();
});
ipcMain.on('settings:close-window', () => settingsWindow?.close());

function isPaused() {
  return pauseUntil !== null;
}

function broadcastPauseState() {
  overlayWindow?.webContents.send('livechat:pause-state', {
    paused: isPaused(),
    until: pauseUntil === Infinity ? null : pauseUntil,
  });
}

function pauseFor(durationMs) {
  clearTimeout(pauseTimer);
  if (durationMs == null) {
    pauseUntil = Infinity;
  } else {
    pauseUntil = Date.now() + durationMs;
    pauseTimer = setTimeout(() => {
      pauseUntil = null;
      broadcastPauseState();
      refreshTrayMenu();
    }, durationMs);
  }
  broadcastPauseState();
  refreshTrayMenu();
}

function resumeLivechat() {
  clearTimeout(pauseTimer);
  pauseUntil = null;
  broadcastPauseState();
  refreshTrayMenu();
}

function buildTrayMenu() {
  const paused = isPaused();
  let statusLabel = `Poissechat v${APP_VERSION}`;
  if (paused) {
    statusLabel = pauseUntil === Infinity
      ? 'Livechat désactivé'
      : `Livechat désactivé jusqu'à ${new Date(pauseUntil).toLocaleTimeString('fr-FR', { hour: '2-digit', minute: '2-digit' })}`;
  }

  return Menu.buildFromTemplate([
    { label: statusLabel, enabled: false },
    { type: 'separator' },
    { label: 'Paramètres', click: () => createSettingsWindow() },
    { label: 'Arrêter le média en cours', click: () => overlayWindow?.webContents.send('media:stop') },
    { type: 'separator' },
    {
      label: 'Désactiver le livechat',
      submenu: [
        { label: '10 minutes', click: () => pauseFor(10 * 60 * 1000) },
        { label: '30 minutes', click: () => pauseFor(30 * 60 * 1000) },
        { label: '1 heure', click: () => pauseFor(60 * 60 * 1000) },
        { label: '24 heures', click: () => pauseFor(24 * 60 * 60 * 1000) },
        { label: 'Arrêt complet', click: () => pauseFor(null) },
      ],
    },
    { label: 'Réactiver le livechat', enabled: paused, click: () => resumeLivechat() },
    { type: 'separator' },
    { label: 'Vérifier les mises à jour', click: () => checkForUpdates() },
    { type: 'separator' },
    { label: 'Quitter', click: () => app.quit() },
  ]);
}

function refreshTrayMenu() {
  tray?.setContextMenu(buildTrayMenu());
}

// Génère un script PowerShell qui attend que l'app se ferme, remplace ses
// fichiers par la nouvelle version extraite du zip, puis la relance. Nécessaire
// car les fichiers de l'app (dll, exe...) sont verrouillés tant qu'elle tourne.
function buildUpdateScript({ zipPath, extractDir, appDir, exeName, mainPid, logPath }) {
  return `
Start-Transcript -Path "${logPath}" -Force | Out-Null

try {
  Write-Host "Attente de la fermeture du process $mainPid..."
  Wait-Process -Id ${mainPid} -Timeout 20 -ErrorAction SilentlyContinue
  Start-Sleep -Seconds 1
  Write-Host "Extraction de ${zipPath} vers ${extractDir}..."

  Remove-Item -Recurse -Force "${extractDir}" -ErrorAction SilentlyContinue
  Add-Type -AssemblyName System.IO.Compression.FileSystem
  [System.IO.Compression.ZipFile]::ExtractToDirectory("${zipPath}", "${extractDir}")
  Write-Host "Extraction terminée."

  $sourceDir = Get-ChildItem -Path "${extractDir}" -Directory | Select-Object -First 1
  Write-Host "Dossier source détecté : $($sourceDir.FullName)"
  $mirrorFlag = "/MIR"
  robocopy $sourceDir.FullName "${appDir}" $mirrorFlag /NFL /NDL /NJH /NJS /NC /NS
  Write-Host "robocopy terminé, code retour : $LASTEXITCODE"

  Remove-Item -Force "${zipPath}" -ErrorAction SilentlyContinue
  Remove-Item -Recurse -Force "${extractDir}" -ErrorAction SilentlyContinue

  $exePath = Join-Path "${appDir}" "${exeName}"
  Write-Host "Relance de $exePath"
  Start-Process -FilePath $exePath
  Write-Host "Relance effectuée."
} catch {
  Write-Host "ERREUR : $($_ | Out-String)"
} finally {
  Stop-Transcript | Out-Null
}
`;
}

async function downloadAndInstallUpdate(zipUrl, expectedSha256) {
  const res = await fetch(zipUrl);
  if (!res.ok) throw new Error(`téléchargement échoué (${res.status})`);
  const buffer = Buffer.from(await res.arrayBuffer());

  // Vérifie que le fichier téléchargé correspond exactement à celui annoncé par
  // version.json, pour détecter une éventuelle altération (MITM, serveur compromis...).
  const actualSha256 = crypto.createHash('sha256').update(buffer).digest('hex');
  if (expectedSha256 && actualSha256 !== expectedSha256) {
    throw new Error(
      `intégrité invalide : le fichier téléchargé ne correspond pas au hash attendu (${actualSha256.slice(0, 12)}... vs ${expectedSha256.slice(0, 12)}...)`
    );
  }

  const tmpDir = app.getPath('temp');
  const stamp = Date.now();
  const zipPath = path.join(tmpDir, `poissechat-update-${stamp}.zip`);
  const extractDir = path.join(tmpDir, `poissechat-update-extract-${stamp}`);
  const scriptPath = path.join(tmpDir, `poissechat-update-${stamp}.ps1`);
  fs.writeFileSync(zipPath, buffer);

  const appDir = path.dirname(process.execPath);
  const exeName = path.basename(process.execPath);
  const logPath = path.join(tmpDir, `poissechat-update-${stamp}.log`);
  fs.writeFileSync(
    scriptPath,
    buildUpdateScript({ zipPath, extractDir, appDir, exeName, mainPid: process.pid, logPath })
  );

  // `cmd /c start` détache vraiment le process du job object d'Electron/Chromium :
  // avec spawn(..., {detached:true}) seul, Windows tue quand même le script quand
  // l'app se ferme (le job object cascade la fermeture à ses enfants).
  spawn(
    'cmd.exe',
    ['/c', 'start', '""', 'powershell.exe', '-NoProfile', '-ExecutionPolicy', 'Bypass', '-WindowStyle', 'Hidden', '-File', scriptPath],
    { detached: true, stdio: 'ignore', windowsHide: true }
  ).unref();

  app.quit();
}

async function checkForUpdates() {
  if (isDev) {
    dialog.showMessageBox({ message: "Mise à jour indisponible en mode développement." });
    return;
  }

  try {
    const res = await fetch(`${BACKEND_URL}/download/version.json`);
    if (!res.ok) throw new Error(`vérification échouée (${res.status})`);
    const { version, url, sha256 } = await res.json();

    if (version === APP_VERSION) {
      dialog.showMessageBox({ message: `Poissechat est déjà à jour (v${APP_VERSION}).` });
      return;
    }

    const { response } = await dialog.showMessageBox({
      type: 'question',
      buttons: ['Mettre à jour', 'Plus tard'],
      defaultId: 0,
      message: `Nouvelle version disponible : v${version} (actuelle : v${APP_VERSION}).`,
      detail: "L'app va se fermer puis redémarrer automatiquement une fois mise à jour.",
    });
    if (response !== 0) return;

    // On ignore volontairement un éventuel host absolu dans `url` : le zip est
    // toujours récupéré depuis notre propre BACKEND_URL, jamais un ailleurs
    // qu'un version.json compromis/usurpé pourrait tenter d'indiquer.
    const zipPath = url.startsWith('http') ? new URL(url).pathname + new URL(url).search : url;
    await downloadAndInstallUpdate(`${BACKEND_URL}${zipPath}`, sha256);
  } catch (err) {
    dialog.showErrorBox('Erreur de mise à jour', err.message);
  }
}

function createTray() {
  tray = new Tray(path.join(__dirname, 'assets', 'tray-icon.png'));
  tray.setToolTip('Poissechat overlay');
  refreshTrayMenu();
}

function createWindow() {
  const { width, height } = screen.getPrimaryDisplay().bounds;

  overlayWindow = new BrowserWindow({
    width,
    height,
    x: 0,
    y: 0,
    icon: appIconPath,
    transparent: true,
    frame: false,
    alwaysOnTop: true,
    skipTaskbar: true,
    resizable: false,
    movable: false,
    hasShadow: false,
    focusable: false,
    webPreferences: {
      preload: path.join(__dirname, 'preload.js'),
      contextIsolation: true,
    },
  });

  overlayWindow.setAlwaysOnTop(true, 'screen-saver');
  overlayWindow.setVisibleOnAllWorkspaces(true, { visibleOnFullScreen: true });
  // Overlay transparente : les clics traversent vers les fenêtres/jeu en dessous.
  overlayWindow.setIgnoreMouseEvents(true, { forward: true });

  if (isDev) {
    overlayWindow.loadURL('http://localhost:5173');
  } else {
    overlayWindow.loadFile(path.join(__dirname, 'dist', 'index.html'));
  }
}

function createSettingsWindow() {
  if (settingsWindow && !settingsWindow.isDestroyed()) {
    settingsWindow.focus();
    return;
  }

  settingsWindow = new BrowserWindow({
    width: 380,
    height: 520,
    resizable: false,
    frame: false,
    icon: appIconPath,
    title: `Paramètres — Poissechat v${APP_VERSION}`,
    webPreferences: {
      preload: path.join(__dirname, 'preload.js'),
      contextIsolation: true,
    },
  });

  if (isDev) {
    settingsWindow.loadURL('http://localhost:5173/#settings');
  } else {
    settingsWindow.loadFile(path.join(__dirname, 'dist', 'index.html'), { hash: 'settings' });
  }

  settingsWindow.on('closed', () => {
    settingsWindow = null;
  });
}

app.whenReady().then(() => {
  // Lance l'app au démarrage de Windows (uniquement une fois packagée : en dev,
  // `electron .` n'est pas une cible utile à enregistrer).
  if (!isDev) {
    app.setLoginItemSettings({ openAtLogin: true });
  }

  createWindow();
  createTray();
});

app.on('window-all-closed', () => {
  if (process.platform !== 'darwin') app.quit();
});
