'use strict';

const net = require('net');
const fs = require('fs');
const {CallbackQueue} = require('../utils/callback_queue');

/**
 * SocketServerJson
 *
 * A generic Unix socket server that handles JSON message communication.
 * Automatically manages socket file lifecycle (cleanup, permissions).
 * Uses callback queues for event handling to support multiple listeners.
 *
 * Features:
 * - Automatic socket file cleanup on startup
 * - Configurable file permissions (default 777 for container use)
 * - JSON message parsing with buffering for partial data
 * - Event callbacks for data, errors, and connection lifecycle
 */
class SocketServerJson {
	constructor(args){
		this.socketFile = args.socketFile;
		this.onData = new CallbackQueue(args.onData, this);
		this.onListen = new CallbackQueue(args.onListen, this);
		this.onError = new CallbackQueue(args.onError);
		this.onClientNew = new CallbackQueue(args.onClientNew);
		this.onClientClose = new CallbackQueue(args.onClientClose);
		this.onClientError = new CallbackQueue(args.onClientError);

		// Set socket file permissions after listening. 660 (owner + group read/write)
		// is the safest default; the Docker image runs both processes as root, and
		// bare-metal operators should ensure the proxy service and openresty share a
		// group when running as separate users. Wrapped in try-catch as chmod may
		// fail in test/restricted environments.
		this.onListen.push(() => {
			try {
				fs.chmodSync(this.socketFile, '660');
			} catch(err) {
				// Chmod may fail in test environments or certain filesystems
				// Socket will still work with default permissions
			}
		});

		this.listen();
	}

	/**
	 * Removes existing socket file if present before creating new server.
	 * Prevents "address already in use" errors from stale socket files.
	 */
	__resetSocketFile(callback){
		let instance = this;

		fs.stat(this.socketFile, function (err, stats) {
			if (stats) {
				fs.unlink(instance.socketFile, function(err){
					if(err){
						// This should never happen
						console.error(err);
					}
					callback(...arguments);
				});
			}else{
				callback();
			}
		});
	}

	/**
	 * Sets up the Unix socket server and registers event handlers.
	 *
	 * Data handling:
	 * - Buffers incoming data to handle partial JSON messages
	 * - Attempts to parse buffer as JSON after each data event
	 * - Clears buffer only after successful parse
	 * - Silent failure on parse errors (waits for more data)
	 */
	__setUpServer(){
		let instance = this;
		this.socket = net.createServer();

		this.socket.on('connection', function(clientSocket){
			let buffer = '';

			clientSocket.on('data', function(data){
				buffer += data.toString();
				try{
					// Parse buffer (not just current data chunk) to handle partial messages
					instance.onData.call(JSON.parse(buffer), clientSocket);
					buffer = '';
				}catch(error){
					// Parse failed - likely incomplete JSON, wait for more data
					// Buffer persists until complete JSON is received
				}
			});

			clientSocket.on('close', instance.onClientClose.call.bind(instance.onClientClose));

			clientSocket.on('error', instance.onClientError.call.bind(instance.onClientError));
		});

		this.socket.on('error', this.onError.call.bind(this.onError));

		this.socket.on('listening', this.onListen.call.bind(this.onListen));
	}

	/**
	 * Starts the Unix socket server.
	 * Cleans up any existing socket file before listening.
	 */
	listen(){
		let instance = this;

		this.__setUpServer();

		this.__resetSocketFile(function(){
			instance.socket.listen(instance.socketFile);
		});
	}
}

module.exports = {SocketServerJson};
