'use strict';

const fs = require('fs');
const os = require('os');
const path = require('path');
const crypto = require('crypto');
const { execFile } = require('child_process');

function assertPrinterName(printerName) {
  const name = String(printerName || '').trim();
  if (!name) throw new Error('printerName no configurado');
  if (/[\r\n]/.test(name)) throw new Error('printerName invalido');
  return name;
}

function psBase64(script) {
  return Buffer.from(script, 'utf16le').toString('base64');
}

function sendBytesToPrinter(printerName, buffer) {
  const name = assertPrinterName(printerName);
  if (!Buffer.isBuffer(buffer)) throw new Error('buffer de impresion invalido');
  if (process.platform !== 'win32') {
    throw new Error('Impresion RAW solo esta implementada para Windows');
  }

  const tmp = path.join(os.tmpdir(), `arkhon_prn_${crypto.randomBytes(8).toString('hex')}.bin`);
  fs.writeFileSync(tmp, buffer);

  const script = `
Add-Type -TypeDefinition @"
using System;
using System.Runtime.InteropServices;
public class RawPrinterHelper {
  [StructLayout(LayoutKind.Sequential, CharSet=CharSet.Ansi)]
  public class DOCINFOA {
    [MarshalAs(UnmanagedType.LPStr)] public string pDocName;
    [MarshalAs(UnmanagedType.LPStr)] public string pOutputFile;
    [MarshalAs(UnmanagedType.LPStr)] public string pDataType;
  }
  [DllImport("winspool.Drv", EntryPoint="OpenPrinterA", SetLastError=true, CharSet=CharSet.Ansi, ExactSpelling=true, CallingConvention=CallingConvention.StdCall)]
  public static extern bool OpenPrinter(string szPrinter, out IntPtr hPrinter, IntPtr pd);
  [DllImport("winspool.Drv", EntryPoint="ClosePrinter", SetLastError=true, ExactSpelling=true, CallingConvention=CallingConvention.StdCall)]
  public static extern bool ClosePrinter(IntPtr hPrinter);
  [DllImport("winspool.Drv", EntryPoint="StartDocPrinterA", SetLastError=true, CharSet=CharSet.Ansi, ExactSpelling=true, CallingConvention=CallingConvention.StdCall)]
  public static extern bool StartDocPrinter(IntPtr hPrinter, Int32 level, [In, MarshalAs(UnmanagedType.LPStruct)] DOCINFOA di);
  [DllImport("winspool.Drv", EntryPoint="EndDocPrinter", SetLastError=true, ExactSpelling=true, CallingConvention=CallingConvention.StdCall)]
  public static extern bool EndDocPrinter(IntPtr hPrinter);
  [DllImport("winspool.Drv", EntryPoint="StartPagePrinter", SetLastError=true, ExactSpelling=true, CallingConvention=CallingConvention.StdCall)]
  public static extern bool StartPagePrinter(IntPtr hPrinter);
  [DllImport("winspool.Drv", EntryPoint="EndPagePrinter", SetLastError=true, ExactSpelling=true, CallingConvention=CallingConvention.StdCall)]
  public static extern bool EndPagePrinter(IntPtr hPrinter);
  [DllImport("winspool.Drv", EntryPoint="WritePrinter", SetLastError=true, ExactSpelling=true, CallingConvention=CallingConvention.StdCall)]
  public static extern bool WritePrinter(IntPtr hPrinter, byte[] pBytes, Int32 dwCount, out Int32 dwWritten);
}
"@
$printer = [Text.Encoding]::UTF8.GetString([Convert]::FromBase64String('${Buffer.from(name, 'utf8').toString('base64')}'))
$file = [Text.Encoding]::UTF8.GetString([Convert]::FromBase64String('${Buffer.from(tmp, 'utf8').toString('base64')}'))
$bytes = [IO.File]::ReadAllBytes($file)
$h = [IntPtr]::Zero
if (-not [RawPrinterHelper]::OpenPrinter($printer, [ref]$h, [IntPtr]::Zero)) { throw "OpenPrinter failed" }
$doc = New-Object RawPrinterHelper+DOCINFOA
$doc.pDocName = "ARKHON Billing QR"
$doc.pDataType = "RAW"
try {
  if (-not [RawPrinterHelper]::StartDocPrinter($h, 1, $doc)) { throw "StartDocPrinter failed" }
  if (-not [RawPrinterHelper]::StartPagePrinter($h)) { throw "StartPagePrinter failed" }
  $written = 0
  if (-not [RawPrinterHelper]::WritePrinter($h, $bytes, $bytes.Length, [ref]$written)) { throw "WritePrinter failed" }
  [RawPrinterHelper]::EndPagePrinter($h) | Out-Null
  [RawPrinterHelper]::EndDocPrinter($h) | Out-Null
} finally {
  [RawPrinterHelper]::ClosePrinter($h) | Out-Null
  Remove-Item -LiteralPath $file -Force -ErrorAction SilentlyContinue
}
`;

  return new Promise((resolve, reject) => {
    execFile('powershell.exe', ['-NoProfile', '-NonInteractive', '-ExecutionPolicy', 'Bypass', '-EncodedCommand', psBase64(script)], { timeout: 30000 }, (err, stdout, stderr) => {
      try { fs.unlinkSync(tmp); } catch (_) {}
      if (err) return reject(new Error((stderr || err.message || 'spooler error').trim()));
      resolve({ ok: true, bytes: buffer.length });
    });
  });
}

module.exports = { sendBytesToPrinter };
