import { ipcMain, BrowserWindow, app, shell } from 'electron';
import * as os from 'os';
import * as path from 'path';
import * as fs from 'fs';
import { exec, spawn } from 'child_process';
import axios from 'axios';
import { expandPath } from './file_utils';

// Helper to execute shell commands
const runExec = (cmd: string): Promise<{ success: boolean; stdout?: string; stderr?: string; error?: string }> => {
  return new Promise((resolve) => {
    exec(cmd, (error, stdout, stderr) => {
      resolve({
        success: !error,
        stdout: stdout.trim(),
        stderr: stderr.trim(),
        error: error ? error.message : undefined
      });
    });
  });
};

let recordingProcess: any = null;

export function registerSystemHandlers() {

  // 1. Window Control
  ipcMain.handle('native:window-control', async (event, { action, value }) => {
    const win = BrowserWindow.fromWebContents(event.sender);
    if (!win) return { success: false, error: 'No window found' };

    switch (action) {
      case 'maximize': win.maximize(); break;
      case 'unmaximize': win.unmaximize(); break;
      case 'minimize': win.minimize(); break;
      case 'restore': win.restore(); break;
      case 'close': win.close(); break;
      case 'devtools':
        if (value) win.webContents.openDevTools();
        else win.webContents.closeDevTools();
        break;
      case 'kiosk': win.setKiosk(!!value); break;
      case 'fullscreen': win.setFullScreen(!!value); break;
      case 'reload': win.reload(); break;
      default: return { success: false, error: `Unknown action: ${action}` };
    }
    return { success: true };
  });

  // 2. Set Wallpaper
  // Supports local path OR URL download. URL images are saved to a stable cache dir
  // so macOS can reference them persistently after reboot.
  // display: 'all' (default) | 'primary' (built-in) | 'external' (projector/second screen)
  // url_external: optional second URL for the external display only.
  ipcMain.handle('native:set-wallpaper', async (event, {
    path: imagePath,
    url,
    url_external,
    display = 'all',
    api_key,
    account_id
  }: {
    path?: string;
    url?: string;
    url_external?: string;
    display?: 'all' | 'primary' | 'external';
    api_key?: string;
    account_id?: string;
  }) => {
    // Cache dir: ~/Library/Caches/OSIT/wallpaper on macOS, ~/.cache/osit/wallpaper on Linux.
    // Using a stable path means macOS keeps the reference across reboots.
    const wallpaper_cache_dir = os.platform() === 'darwin'
      ? path.join(os.homedir(), 'Library', 'Caches', 'OSIT', 'wallpaper')
      : path.join(os.homedir(), '.cache', 'osit', 'wallpaper');

    async function download_wallpaper_image(image_url: string, basename: string): Promise<{ success: boolean; path?: string; error?: string }> {
      if (!fs.existsSync(wallpaper_cache_dir)) {
        fs.mkdirSync(wallpaper_cache_dir, { recursive: true });
      }

      // Infer extension from URL path, fall back to .jpg
      let ext = '.jpg';
      try {
        const url_path = new URL(image_url).pathname;
        const inferred = path.extname(url_path).toLowerCase();
        if (['.jpg', '.jpeg', '.png', '.webp'].includes(inferred)) {
          ext = inferred === '.jpeg' ? '.jpg' : inferred;
        }
      } catch {}

      const dest_path = path.join(wallpaper_cache_dir, basename + ext);

      const headers: Record<string, string> = {};
      if (api_key) headers['x-aether-api-key'] = api_key;
      if (account_id) headers['x-account-id'] = account_id;

      try {
        const response = await axios({ method: 'get', url: image_url, responseType: 'stream', headers });
        const content_type = (response.headers['content-type'] ?? '') as string;
        if (!content_type.startsWith('image/')) {
          response.data.destroy();
          return { success: false, error: `URL did not return an image (Content-Type: ${content_type})` };
        }

        const writer = fs.createWriteStream(dest_path);
        response.data.pipe(writer);
        await new Promise<void>((resolve, reject) => {
          writer.on('finish', resolve);
          writer.on('error', reject);
        });
        const file_size = fs.statSync(dest_path).size;
        if (file_size === 0) {
          try { fs.unlinkSync(dest_path); } catch {}
          return { success: false, error: 'Wallpaper download incomplete (0 bytes)' };
        }
        return { success: true, path: dest_path };
      } catch (e: any) {
        return { success: false, error: `Wallpaper download failed: ${e.message}` };
      }
    }

    // HARDENED: write AppleScript to a temp file, same pattern as native:run-osascript.
    // The old osascript -e approach breaks on paths with spaces or special characters.
    async function apply_mac_wallpaper(img_path: string, display_target: 'all' | 'primary' | 'external'): Promise<{ success: boolean; stdout?: string; stderr?: string; error?: string }> {
      const escaped = img_path.replace(/\\/g, '\\\\').replace(/"/g, '\\"');
      let script: string;
      if (display_target === 'primary') {
        script = `tell application "System Events"\n\ttell desktop 1\n\t\tset picture to "${escaped}"\n\tend tell\nend tell`;
      } else if (display_target === 'external') {
        script = `tell application "System Events"\n\ttell desktop 2\n\t\tset picture to "${escaped}"\n\tend tell\nend tell`;
      } else {
        script = `tell application "System Events"\n\ttell every desktop\n\t\tset picture to "${escaped}"\n\tend tell\nend tell`;
      }

      const script_path = path.join(os.tmpdir(), `ae_wallpaper_${Date.now()}.scpt`);
      fs.writeFileSync(script_path, script, 'utf-8');
      try {
        return await runExec(`osascript "${script_path}"`);
      } finally {
        try { fs.unlinkSync(script_path); } catch {}
      }
    }

    if (os.platform() === 'darwin') {
      // Resolve primary image path
      let primary_path: string | null = null;

      if (imagePath) {
        const clean = expandPath(imagePath);
        if (!fs.existsSync(clean)) return { success: false, error: 'Image file not found' };
        primary_path = clean;
      } else if (url) {
        const result = await download_wallpaper_image(url, 'wallpaper_primary');
        if (!result.success || !result.path) return { success: false, error: result.error };
        primary_path = result.path;
      }

      if (!primary_path && url_external && display === 'external') {
        const ext_result = await download_wallpaper_image(url_external, 'wallpaper_external');
        if (!ext_result.success || !ext_result.path) return { success: false, error: ext_result.error };
        return await apply_mac_wallpaper(ext_result.path, 'external');
      }

      if (!primary_path) return { success: false, error: 'No image source provided' };

      if (url_external) {
        // Different images for each display: set primary display first, then external
        const primary_result = await apply_mac_wallpaper(primary_path, 'primary');
        if (!primary_result.success) return primary_result;

        const ext_result = await download_wallpaper_image(url_external, 'wallpaper_external');
        if (!ext_result.success || !ext_result.path) return { success: false, error: ext_result.error };
        return await apply_mac_wallpaper(ext_result.path, 'external');
      } else {
        return await apply_mac_wallpaper(primary_path, display);
      }
    }

    if (os.platform() === 'linux') {
      // Dev test mode: never touch the desktop on Linux. Running gsettings during
      // development would reset the dev workstation monitors on every test cycle.
      // Return what would have run so the Svelte side can show a debug popup.
      const would_run: string[] = [];
      const cache_dir = wallpaper_cache_dir;

      if (!imagePath && !url && !url_external) {
        return { success: false, error: 'No image source provided' };
      }

      if (imagePath) {
        const clean = expandPath(imagePath);
        if (!fs.existsSync(clean)) return { success: false, error: 'Image file not found' };
      }

      if (url) would_run.push(`download: ${url}\n  → ${path.join(cache_dir, 'wallpaper_primary.jpg')}`);
      if (url_external) would_run.push(`download: ${url_external}\n  → ${path.join(cache_dir, 'wallpaper_external.jpg')}`);

      if (imagePath) {
        would_run.push(`gsettings set org.gnome.desktop.background picture-uri "file://${expandPath(imagePath)}"`);
      } else if (url) {
        would_run.push(`gsettings set org.gnome.desktop.background picture-uri "file://${path.join(cache_dir, 'wallpaper_primary.jpg')}"`);
      }
      if (url_external) {
        would_run.push(`(external display: gsettings has no per-display wallpaper support)`);
      }

      return {
        success: true,
        linux_test_mode: true,
        platform: 'linux',
        display,
        url: url ?? null,
        url_external: url_external ?? null,
        would_run
      };
    }

    return { success: false, error: 'Platform not supported' };
  });

  // 3. Power Control
  ipcMain.handle('native:power-control', async (event, { action }) => {
    let cmd = '';
    const isMac = os.platform() === 'darwin';
    const isLinux = os.platform() === 'linux';

    if (action === 'shutdown') {
      if (isMac) cmd = 'shutdown -h now'; // Requires sudo/admin usually
      if (isLinux) cmd = 'shutdown -h now';
    } else if (action === 'reboot') {
      if (isMac) cmd = 'shutdown -r now';
      if (isLinux) cmd = 'reboot';
    } else if (action === 'sleep') {
      if (isMac) cmd = 'pmset sleepnow';
      if (isLinux) cmd = 'systemctl suspend';
    }

    if (!cmd) return { success: false, error: 'Action not supported' };

    // NOTE: These commands often require root.
    // For a kiosk, you might configure sudoers to allow this specific command without password.
    return await runExec(cmd);
  });

  // 4. Open External (Browser)
  ipcMain.handle('native:open-external', async (event, { url, app: appName }) => {
    if (appName === 'chrome') {
      if (os.platform() === 'darwin') {
        return await runExec(`open -a "Google Chrome" "${url}"`);
      } else if (os.platform() === 'linux') {
        return await runExec(`google-chrome "${url}"`);
      }
    } else if (appName === 'firefox') {
        if (os.platform() === 'darwin') {
            return await runExec(`open -a "Firefox" "${url}"`);
        } else if (os.platform() === 'linux') {
            return await runExec(`firefox "${url}"`);
        }
    }

    // Default system handler
    await shell.openExternal(url);
    return { success: true };
  });

  // 5. Manage Recording (Aperture Wrapper)
  ipcMain.handle('native:manage-recording', async (event, { action, options }) => {
    if (os.platform() !== 'darwin') return { success: false, error: 'Recording only supported on macOS' };

    // Path to bundled aperture binary
    // In dev: ./resources/bin/aperture
    // In prod: process.resourcesPath/bin/aperture
    const binPath = app.isPackaged
      ? path.join(process.resourcesPath, 'bin', 'aperture')
      : path.join(__dirname, '../../resources/bin/aperture'); // Adjust based on structure

    if (action === 'start') {
      if (recordingProcess) return { success: false, error: 'Recording already in progress' };

      const { fps = 30, audioDeviceId, output } = options || {};
      const cleanOutput = expandPath(output || '~/tmp/recording.mp4');

      const args = ['run', '--fps', fps, '--output', cleanOutput];
      if (audioDeviceId) args.push('--audio-device-id', audioDeviceId);

      // Spawn process
      // Note: aperture is a CLI tool. We might need 'aperture' node package or the binary.
      // Assuming binary usage here.
      try {
        console.log(`Starting recording: ${binPath} ${args.join(' ')}`);
        recordingProcess = spawn(binPath, args);

        recordingProcess.on('error', (err: any) => {
            console.error('Recording error:', err);
            recordingProcess = null;
        });

        recordingProcess.on('exit', (code: any) => {
            console.log(`Recording exited with code ${code}`);
            recordingProcess = null;
        });

        return { success: true, pid: recordingProcess.pid };
      } catch (e: any) {
        return { success: false, error: e.message };
      }

    } else if (action === 'stop') {
      if (!recordingProcess) return { success: false, error: 'No recording in progress' };

      recordingProcess.kill('SIGINT'); // Send interrupt to stop cleanly
      recordingProcess = null;
      return { success: true };
    } else if (action === 'status') {
      return { success: true, isRecording: !!recordingProcess };
    }

    return { success: false, error: 'Unknown action' };
  });

  // 6. Set Display Layout (Displayplacer)
  // Auto-detects connected displays via `displayplacer list` when no explicit configStr is given.
  // mirror: secondary display(s) mirror the primary.
  // extend: displays shown side-by-side; un-mirrors if currently mirrored.
  ipcMain.handle('native:set-display-layout', async (event, { mode, configStr }) => {
    if (os.platform() !== 'darwin') return { success: false, error: 'Display control only supported on macOS' };

    const binPath = app.isPackaged
      ? path.join(process.resourcesPath, 'bin', 'displayplacer')
      : path.join(__dirname, '../../resources/bin/displayplacer');

    // Explicit config string always takes priority — allows manual override per device.
    if (configStr) {
      return await runExec(`"${binPath}" ${configStr}`);
    }

    // Auto-detect: `displayplacer list` emits a ready-to-run command line at the bottom.
    // We parse the quoted display strings from that line and modify them for the requested mode.
    const list_result = await runExec(`"${binPath}" list`);
    if (!list_result.success || !list_result.stdout) {
      return { success: false, error: `displayplacer list failed: ${list_result.error ?? 'no output'}` };
    }

    // The command line looks like: displayplacer "id:xxx res:... origin:(0,0) ..." "id:yyy ..."
    const cmd_line = list_result.stdout.split('\n').find(l => l.trim().startsWith('displayplacer "'));
    if (!cmd_line) {
      return { success: false, error: 'Only one display connected or displayplacer list output unrecognised' };
    }

    const display_strings = [...cmd_line.matchAll(/"([^"]+)"/g)].map(m => m[1]);
    if (display_strings.length < 2) {
      return { success: false, error: 'Only one display found; cannot change layout' };
    }

    if (mode === 'mirror') {
      const primary_id_match = display_strings[0].match(/\bid:([^\s]+)/);
      if (!primary_id_match) {
        return { success: false, error: 'Could not parse primary display ID from displayplacer output' };
      }
      const primary_id = primary_id_match[1];

      // Primary display unchanged; secondary display(s) get mirror:<primary_id>.
      const mirror_args = display_strings.map((s, i) => {
        if (i === 0) return `"${s}"`;
        const without_existing_mirror = s.replace(/\s*mirror:\S+/g, '').trim();
        return `"${without_existing_mirror} mirror:${primary_id}"`;
      }).join(' ');

      return await runExec(`"${binPath}" ${mirror_args}`);
    }

    if (mode === 'extend') {
      const any_mirrored = display_strings.some(s => /\bmirror:\S+/.test(s));
      if (!any_mirrored) {
        // Already extended — re-apply current layout to ensure it is active.
        return await runExec(`"${binPath}" ${display_strings.map(s => `"${s}"`).join(' ')}`);
      }

      // Remove mirror keys and compute side-by-side origins from each display's resolution.
      let x_offset = 0;
      const extend_args = display_strings.map((s) => {
        const without_mirror = s.replace(/\s*mirror:\S+/g, '').trim();
        const res_match = without_mirror.match(/\bres:(\d+)x\d+/);
        const width = res_match ? parseInt(res_match[1]) : 1920;
        const updated = without_mirror.replace(/\borigin:\([^)]+\)/, `origin:(${x_offset},0)`);
        x_offset += width;
        return `"${updated}"`;
      }).join(' ');

      return await runExec(`"${binPath}" ${extend_args}`);
    }

    return { success: false, error: `Unsupported display mode: ${mode}` };
  });

  // 7. Update App
  ipcMain.handle('native:update-app', async (event, { source, url, path: localPath }) => {
    // 1. Determine Source File
    let updateFile = '';
    const tempDir = os.tmpdir();
    const destName = 'update_package.zip'; // Or .app, .AppImage
    const destPath = path.join(tempDir, destName);

    if (source === 'url' && url) {
        // Download
        try {
            const response = await axios({
                method: 'get',
                url: url,
                responseType: 'stream'
            });

            const writer = fs.createWriteStream(destPath);
            response.data.pipe(writer);

            await new Promise((resolve, reject) => {
                writer.on('finish', () => resolve(true));
                writer.on('error', reject);
            });
            updateFile = destPath;
        } catch (e: any) {
            return { success: false, error: `Download failed: ${e.message}` };
        }
    } else if (source === 'file' && localPath) {
        const cleanLocal = expandPath(localPath);
        if (fs.existsSync(cleanLocal)) {
            updateFile = cleanLocal;
        } else {
            return { success: false, error: 'Local update file not found' };
        }
    }

    if (!updateFile) return { success: false, error: 'No update source provided' };

    // 2. Install Logic (Stub)
    // Real implementation depends on OS and packaging format.
    // macOS: Mount DMG, copy .app to /Applications? Or Unzip .app?
    // Linux: chmod +x AppImage and move?

    console.log(`Ready to install update from: ${updateFile}`);

    // For now, just return success so the UI knows we "downloaded" it.
    return { success: true, message: 'Update downloaded/located. Installation logic requires packaging specifics.', downloadedPath: updateFile };
  });
}
