const EventEmitter = require("events");
const fs = require("fs");
const path = require("path");
const zlib = require("zlib");
const { DATA_DIR } = require("./paths");

const mitmEvents = new EventEmitter();

// History buffer for SSE connections
const logHistory = [];
const MAX_HISTORY = 100;

function addToHistory(type, message) {
  logHistory.push({ type, message });
  if (logHistory.length > MAX_HISTORY) logHistory.shift();
  mitmEvents.emit("log", { type, message });
}

function log(msg) {
  console.log(`[MITM] ${msg}`);
  addToHistory("log", msg);
}

function err(msg) {
  console.error(`[MITM ERROR] ${msg}`);
  addToHistory("error", msg);
}

function onLog(callback) {
  // Send history immediately on connection
  logHistory.forEach(entry => callback(entry.message));
  mitmEvents.on("log", (data) => callback(data.message));
}

// ─── Request / Response Dumping for Debugging (Origin features) ───────────────

const DUMP_DIR = path.join(DATA_DIR, "logs", "mitm");

function getDumpFilePath(tool, endpoint, type, requestId) {
  fs.mkdirSync(DUMP_DIR, { recursive: true });
  // Format: copilot_chat_completions_123456789_req.json
  const safeEndpoint = endpoint.replace(/[^a-zA-Z0-9]/g, "_").replace(/_+/g, "_").replace(/^_|_$/g, "");
  return path.join(DUMP_DIR, `${tool}_${safeEndpoint}_${requestId}_${type}.txt`);
}

function dumpRequest(req, bodyBuffer, tool, targetHost) {
  try {
    const requestId = Date.now();
    // Attach requestId to request object so response dumper can use it
    req.mitmRequestId = requestId;
    req.mitmTool = tool || "unknown";

    const dumpFile = getDumpFilePath(tool || "unknown", req.url, "req", requestId);
    const methodLine = `${req.method} https://${targetHost}${req.url} HTTP/${req.httpVersion}`;
    const headerLines = Object.entries(req.headers).map(([k, v]) => `${k}: ${v}`).join("\n");
    let bodyStr = "";
    if (bodyBuffer && bodyBuffer.length > 0) {
      try {
        bodyStr = JSON.stringify(JSON.parse(bodyBuffer.toString()), null, 2);
      } catch {
        bodyStr = bodyBuffer.toString("utf8");
      }
    }

    fs.writeFileSync(dumpFile, `${methodLine}\n${headerLines}\n\n${bodyStr}`);
  } catch (e) {
    err(`Failed to dump request: ${e.message}`);
  }
}

/**
 * Creates a stateful object to stream the response headers/body to a file.
 * Handles gzip/deflate/brotli decoding automatically.
 */
function createResponseDumper(req, tag) {
  const requestId = req.mitmRequestId || Date.now();
  const tool = req.mitmTool || "unknown";
  const dumpFile = getDumpFilePath(tool, `${req.url}_${tag}`, "res", requestId);
  
  let fd;
  try {
    fs.mkdirSync(DUMP_DIR, { recursive: true });
    fd = fs.openSync(dumpFile, "w");
  } catch (e) {
    err(`Failed to open dump file ${dumpFile}: ${e.message}`);
    return null;
  }

  let decompressor = null;
  
  return {
    writeHeader(status, headers) {
      try {
        const headerLines = Object.entries(headers).map(([k, v]) => `${k}: ${v}`).join("\n");
        fs.writeSync(fd, `HTTP/${req.httpVersion} ${status}\n${headerLines}\n\n`);
        
        // Setup transparent decompression for the body stream
        const encoding = (headers["content-encoding"] || "").toLowerCase();
        if (encoding === "gzip") decompressor = zlib.createGunzip();
        else if (encoding === "deflate") decompressor = zlib.createInflate();
        else if (encoding === "br") decompressor = zlib.createBrotliDecompress();

        if (decompressor) {
          decompressor.on("data", (decodedChunk) => {
            try { fs.writeSync(fd, decodedChunk); } catch (e) {}
          });
          decompressor.on("error", (e) => {
            try { fs.writeSync(fd, `\n[Decompression Error: ${e.message}]\n`); } catch (e) {}
          });
        }
      } catch (e) {}
    },
    
    writeChunk(chunk) {
      try {
        if (!chunk) return;
        if (decompressor) {
          decompressor.write(chunk);
        } else {
          fs.writeSync(fd, chunk);
        }
      } catch (e) {}
    },
    
    end() {
      try {
        if (decompressor) {
          decompressor.end();
          // Let it flush, then close FD slightly later
          setTimeout(() => { try { fs.closeSync(fd); } catch (e) {} }, 50);
        } else {
          fs.closeSync(fd);
        }
      } catch (e) {}
    }
  };
}

function clearDumpDir() {
  try {
    if (fs.existsSync(DUMP_DIR)) {
      const files = fs.readdirSync(DUMP_DIR);
      // keep only the latest 100
      if (files.length > 100) {
        files
          .map(f => ({ name: f, time: fs.statSync(path.join(DUMP_DIR, f)).mtime.getTime() }))
          .sort((a, b) => b.time - a.time)
          .slice(100)
          .forEach(f => fs.unlinkSync(path.join(DUMP_DIR, f.name)));
      }
    }
  } catch (e) {}
}

module.exports = {
  log,
  err,
  onLog,
  dumpRequest,
  createResponseDumper,
  clearDumpDir
};
