import fs from "node:fs/promises"
import { existsSync, mkdirSync, writeFileSync } from "node:fs"
import path from "node:path"
import { execSync, spawn } from "node:child_process"
import defaults from "linebridge/dist/defaults"

const localNginxBinary = path.resolve(process.cwd(), "nginx-bin")
const serverPkg = require("../../../package.json")

export default class NginxManager {
	constructor(options = {}) {
		this.options = options

		this.ssl = {
			on: false,
			cert_file_name: null,
			key_file_name: null,
		}
		this.port = options.port || 9000
		this.internalIp = options.internalIp || "0.0.0.0"

		// Set binary path
		this.nginxBinary = existsSync(localNginxBinary)
			? localNginxBinary
			: "nginx"

		// Directory structure
		this.nginxWorkDir =
			options.nginxWorkDir || path.join(process.cwd(), ".nginx")
		this.configDir = path.join(this.nginxWorkDir, "conf")
		this.tempDir = path.join(this.nginxWorkDir, "temp")
		this.logsDir = path.join(this.tempDir, "logs")
		this.cacheDir = path.join(this.tempDir, "cache")

		// Configuration files
		this.mainConfigPath = path.join(this.configDir, "nginx.conf")
		this.servicesConfigPath = path.join(this.configDir, "services.conf")

		// Process reference
		this.nginxProcess = null
		this.isNginxRunning = false

		if (
			existsSync(this.options.cert_file_name) &&
			existsSync(this.options.key_file_name)
		) {
			console.log("[nginx] Setting SSL listen mode")
			this.ssl.on = true
			this.ssl.cert_file_name = this.options.cert_file_name
			this.ssl.key_file_name = this.options.key_file_name
		}
	}

	routes = new Map() // key: path, value: { serviceId, target, pathRewrite, ws }

	/**
	 * Initialize the directory structure and configuration files
	 */
	async initialize() {
		try {
			// Create directories
			this._ensureDirectories()

			// Create mime.types file
			await this.writeMimeTypes()

			// Generate main config file
			await this.generateMainConfig()

			console.log(`🔧 Using Nginx binary: ${this.nginxBinary}`)
			return true
		} catch (error) {
			console.error("❌ Failed to initialize Nginx configuration:", error)
			return false
		}
	}

	/**
	 * Ensure all required directories exist
	 */
	_ensureDirectories() {
		const dirs = [
			this.configDir,
			this.tempDir,
			this.logsDir,
			this.cacheDir,
			path.join(this.cacheDir, "client_body"),
			path.join(this.cacheDir, "proxy"),
		]

		// Create all directories
		for (const dir of dirs) {
			if (!existsSync(dir)) {
				mkdirSync(dir, { recursive: true })
			}
		}

		// Create empty log files
		const logFiles = [
			path.join(this.logsDir, "access.log"),
			path.join(this.logsDir, "error.log"),
		]

		for (const file of logFiles) {
			if (!existsSync(file)) {
				writeFileSync(file, "")
			}
		}
	}

	/**
	 * Generate the main Nginx configuration file
	 */
	async generateMainConfig() {
		// Normalize paths for Nginx
		const normalizedConfigDir = this.configDir.replace(/\\/g, "/")
		const normalizedTempDir = this.tempDir.replace(/\\/g, "/")
		const normalizedLogsDir = path
			.join(this.tempDir, "logs")
			.replace(/\\/g, "/")
		const normalizedCacheDir = path
			.join(this.tempDir, "cache")
			.replace(/\\/g, "/")

		const mainEndpointJSON = JSON.stringify({
			name: serverPkg.name,
			version: serverPkg.version,
			lb_version: defaults?.version ?? "unknown",
			gateway: "nginx",
		})

		const config = `
# Nginx configuration for Comty API Gateway
# Auto-generated - Do not edit manually

worker_processes auto;
error_log ${normalizedLogsDir}/error.log ${debugFlag ? "debug" : "error"};
pid ${normalizedTempDir}/nginx.pid;

events {
    worker_connections 1024;
}

http {
    include ${normalizedConfigDir}/mime.types;

    log_format main '$remote_addr - $remote_user [$time_local] "$request" '
                    '$status $body_bytes_sent "$http_referer" '
                    '"$http_user_agent" "$http_x_forwarded_for"';

    log_format debug '$remote_addr - $remote_user [$time_local] "$request" '
                    '$status $body_bytes_sent "$http_referer" '
                    '"$http_user_agent" "$http_x_forwarded_for" '
                    'upstream_addr: $upstream_addr '
                    'upstream_status: $upstream_status '
                    'request_time: $request_time '
                    'http_version: $server_protocol';

    access_log ${normalizedLogsDir}/access.log ${debugFlag ? "debug" : "main"};

    sendfile on;
    tcp_nodelay on;

    client_max_body_size 100M;

    # Temp directories
    client_body_temp_path ${normalizedCacheDir}/client_body;
    proxy_temp_path ${normalizedCacheDir}/proxy;

    # Set proxy timeouts
    proxy_connect_timeout 60s;
    proxy_read_timeout 60s;
    proxy_send_timeout 60s;

    map $http_x_forwarded_proto $proxy_x_forwarded_proto {
      default $http_x_forwarded_proto;
      ''      $scheme;
    }

    server {
            ${this.ssl.on ? `listen ${this.port} ssl;` : `listen ${this.port};`}
            server_name _;

            ${this.ssl.cert_file_name ? `ssl_certificate ${this.ssl.cert_file_name};` : ""}
            ${this.ssl.key_file_name ? `ssl_certificate_key ${this.ssl.key_file_name};` : ""}



            # Include service-specific configurations
            include ${normalizedConfigDir}/services.conf;

            # Default route for /
            location = / {
              add_header Content-Type application/json;
              add_header 'Access-Control-Allow-Origin' '*' always;
              add_header 'Access-Control-Allow-Headers' '*' always;
              add_header 'Access-Control-Allow-Methods' 'GET,HEAD,PUT,PATCH,POST,DELETE' always;

              return 200 '${mainEndpointJSON}';
            }

            # Catch-all for any other unmatched path
            location / {
              add_header Content-Type application/json;
              add_header 'Access-Control-Allow-Origin' '*' always;
              add_header 'Access-Control-Allow-Headers' '*' always;
              add_header 'Access-Control-Allow-Methods' 'GET,HEAD,PUT,PATCH,POST,DELETE' always;

              return 404 '{"error":"Not found"}';
            }
        }
    }
`

		console.log(`📝 Nginx configuration initialized at ${this.configDir}`)

		await fs.writeFile(this.mainConfigPath, config)
	}

	// Create mime.types file if it doesn't exist
	async writeMimeTypes() {
		const mimeTypesPath = path.join(this.configDir, "mime.types")

		if (!existsSync(mimeTypesPath)) {
			// Basic MIME types
			const mimeTypes = `types {
    text/html                                        html htm shtml;
    text/css                                         css;
    text/xml                                         xml;
    image/gif                                        gif;
    image/jpeg                                       jpeg jpg;
    application/javascript                           js;
    text/plain                                       txt;
    image/png                                        png;
    image/svg+xml                                    svg svgz;
    application/json                                 json;
}`

			await fs.writeFile(mimeTypesPath, mimeTypes)
		}
	}

	/**
	 * Register a new service route in Nginx - queues for batch processing
	 * @param {Object} routeConfig - Route configuration
	 * @returns {Boolean} - Success status
	 */
	async register(routeConfig) {
		try {
			const {
				serviceId,
				path: routePath,
				target,
				pathRewrite,
				websocket,
			} = routeConfig

			// Normalize path
			let normalizedPath = routePath.startsWith("/")
				? routePath
				: `/${routePath}`

			if (debugFlag) {
				console.log(
					`🔍 Registering route for [${serviceId}]: ${normalizedPath} -> ${target}`,
				)
			}

			// Store the route with improved handling of path rewrites
			const effectivePathRewrite = pathRewrite || {}

			this.routes.set(normalizedPath, {
				serviceId: serviceId,
				target: target,
				pathRewrite: effectivePathRewrite,
				websocket: !!websocket,
			})

			return true
		} catch (error) {
			console.error(
				`❌ Failed to register route for [${routeConfig.serviceId}]:`,
				error,
			)
			return false
		}
	}

	/**
	 * Apply the current configuration (generate config and reload/start Nginx)
	 */
	async applyConfiguration() {
		try {
			console.log(
				`🔄 Applying configuration with ${this.routes.size} routes...`,
			)

			// Generate services configuration
			await this.regenerateServicesConfig()

			// Verify configuration is valid
			const configTest = this.execNginxCommand(
				["-t", "-c", this.mainConfigPath],
				true,
			)
			if (!configTest.success) {
				throw new Error(
					`Configuration validation failed: ${configTest.error}`,
				)
			}

			console.log(`✅ Configuration applied successfully`)
		} catch (error) {
			console.error(`❌ Failed to apply configuration:`, error)
		}
	}

	/**
	 * Unregister all routes for a specific service
	 * @param {String} serviceId - Service ID to unregister
	 * @returns {Boolean} - Success status
	 */
	async unregisterAllFromService(serviceId) {
		try {
			// Find and remove all routes for this service
			for (const [path, route] of this.routes.entries()) {
				if (route.serviceId === serviceId) {
					this.routes.delete(path)
				}
			}

			console.log(`📝 Removed routes for service [${serviceId}]`)

			return true
		} catch (error) {
			console.error(
				`❌ Failed to unregister routes for service [${serviceId}]:`,
				error,
			)
			return false
		}
	}

	/**
	 * Regenerate the services configuration file
	 */
	async regenerateServicesConfig() {
		let config = `# Service routes\n# Last updated: ${new Date().toISOString()}\n# Total routes: ${this.routes.size}\n\n`

		// Special case - no routes yet
		if (this.routes.size === 0) {
			config += "# No services registered yet\n"
			await fs.writeFile(this.servicesConfigPath, config)
			return
		}

		// Add all routes
		for (const [path, route] of this.routes.entries()) {
			config += this.generateLocationBlock(path, route)
		}

		// Write the config
		await fs.writeFile(this.servicesConfigPath, config)

		if (debugFlag) {
			console.log(`📄 Writted [${this.routes.size}] service routes`)
		}
	}

	/**
	 * Generate a location block for a route
	 * @param {String} path - Route path
	 * @param {Object} route - Route configuration
	 * @returns {String} - Nginx location block
	 */
	generateLocationBlock(path, route) {
		// Create rewrite configuration if needed
		let rewriteConfig = ""

		if (route.pathRewrite && Object.keys(route.pathRewrite).length > 0) {
			rewriteConfig += "# Path rewrite rules\n"

			for (const [pattern, replacement] of Object.entries(
				route.pathRewrite,
			)) {
				// Improved rewrite pattern that preserves query parameters
				rewriteConfig += `\nrewrite ${pattern} ${replacement} break;`
			}
		}

		// Determine if this is a root location or a more specific path
		const locationDirective =
			path === "/" ? "location /" : `location ${path}`

		// Build the full location block with proper indentation
		return `
${locationDirective} {
    if ($request_method = OPTIONS) {
      add_header 'Access-Control-Allow-Origin' '*';
      add_header 'Access-Control-Allow-Headers' '*';
      add_header 'Access-Control-Allow-Methods' 'GET,HEAD,PUT,PATCH,POST,DELETE';

      return 200;
    }

    # Add some missing headers
    add_header 'X-Accel-Buffering' 'no';

    # Set proxy configuration
    proxy_http_version 1.1;
    proxy_pass_request_headers on;
    chunked_transfer_encoding off;
    proxy_buffering off;
    proxy_cache off;

    # Standard proxy headers
    proxy_set_header Host $http_host;

    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $proxy_x_forwarded_proto;

    # Set headers for WebSocket support
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";

    ${rewriteConfig}

    # Proxy pass to service
    proxy_pass ${route.target};
}
`
	}

	/**
	 * Start the Nginx server
	 * @returns {Boolean} - Success status
	 */
	async start() {
		try {
			// Start Nginx
			this.nginxProcess = spawn(
				this.nginxBinary,
				[
					"-c",
					this.mainConfigPath,
					"-g",
					"daemon off;",
					"-p",
					this.tempDir,
				],
				{
					stdio: ["ignore", "pipe", "pipe"],
				},
			)

			this.nginxProcess.stdout.on("data", (data) => {
				console.log(`[Nginx] ${data.toString().trim()}`)
			})

			this.nginxProcess.stderr.on("data", (data) => {
				console.error(`[Nginx] ${data.toString().trim()}`)
			})

			this.nginxProcess.on("close", (code) => {
				this.isNginxRunning = false
				if (code !== 0 && code !== null) {
					console.error(`Nginx process exited with code ${code}`)
				}
				this.nginxProcess = null
			})

			// Wait briefly to check for immediate startup errors
			await new Promise((resolve) => setTimeout(resolve, 500))

			if (this.nginxProcess.exitCode !== null) {
				throw new Error(
					`Nginx failed to start (exit code: ${this.nginxProcess.exitCode})`,
				)
			}

			this.isNginxRunning = true
			console.log(`🚀 Nginx started on port ${this.port}`)
			return true
		} catch (error) {
			this.isNginxRunning = false
			console.error("❌ Failed to start Nginx:", error.message)
			return false
		}
	}

	/**
	 * Execute an Nginx command
	 * @param {Array} args - Command arguments
	 * @param {Boolean} returnOutput - Whether to return command output
	 * @returns {Object} - Success status and output/error
	 */
	execNginxCommand(args, returnOutput = false) {
		try {
			// Always include prefix to set the temp directory
			const allArgs = [...args, "-p", this.tempDir]

			const cmdString = `"${this.nginxBinary}" ${allArgs.join(" ")}`

			if (debugFlag) {
				console.log(`🔍 Executing: ${cmdString}`)
			}

			const output = execSync(cmdString, {
				encoding: "utf8",
				stdio: returnOutput ? "pipe" : "inherit",
			})

			return {
				success: true,
				output: returnOutput ? output : null,
			}
		} catch (error) {
			return {
				success: false,
				error: error.message,
				output: error.stdout,
			}
		}
	}

	/**
	 * Reload the Nginx configuration
	 * @returns {Boolean} - Success status
	 */
	async reload() {
		try {
			// Test configuration validity
			const configTest = this.execNginxCommand(
				["-t", "-c", this.mainConfigPath],
				true,
			)
			if (!configTest.success) {
				throw new Error(
					`Configuration test failed: ${configTest.error}`,
				)
			}

			// If Nginx isn't running, start it
			if (
				!this.isNginxRunning ||
				!this.nginxProcess ||
				this.nginxProcess.exitCode !== null
			) {
				return await this.start()
			}

			// Send reload signal
			const result = this.execNginxCommand([
				"-s",
				"reload",
				"-c",
				this.mainConfigPath,
			])

			if (!result.success) {
				throw new Error(`Failed to reload Nginx: ${result.error}`)
			}

			console.log("🔄 Nginx configuration reloaded")
			return true
		} catch (error) {
			console.error("❌ Failed to reload Nginx:", error.message)
			return false
		}
	}

	/**
	 * Stop the Nginx server
	 * @returns {Boolean} - Success status
	 */
	async stop() {
		try {
			if (this.nginxProcess) {
				// Try graceful shutdown first
				this.execNginxCommand(["-s", "quit", "-c", this.mainConfigPath])

				// Give Nginx time to shut down
				await new Promise((resolve) => setTimeout(resolve, 1000))

				// If still running, force kill
				if (this.nginxProcess && this.nginxProcess.exitCode === null) {
					this.nginxProcess.kill("SIGTERM")

					// If STILL running after another second, use SIGKILL
					await new Promise((resolve) => setTimeout(resolve, 1000))
					if (
						this.nginxProcess &&
						this.nginxProcess.exitCode === null
					) {
						this.nginxProcess.kill("SIGKILL")
					}
				}

				this.nginxProcess = null
			}

			this.isNginxRunning = false
			console.log("🛑 Nginx stopped")
			return true
		} catch (error) {
			console.error("❌ Failed to stop Nginx:", error.message)
			return false
		}
	}
}
