import Docker from 'dockerode';
import { promisify } from 'util';
import { exec } from 'child_process';
import path from 'path';
const execPromise = promisify(exec);

export function setupDocker() {
  return new Docker({ socketPath: process.env.DOCKER_SOCKET_PATH });
}

export async function getContainerStats(docker, containerName) {
  try {
    const container = docker.getContainer(containerName);
    const [containers, info, stats] = await Promise.all([
      docker.listContainers({ all: true }),
      container.inspect(),
      container.stats({ stream: false })
    ]);

    if (!containers.some(c => c.Names.includes(`/${containerName}`))) {
      return { error: `Container ${containerName} not found` };
    }

    const memoryUsage = stats.memory_stats.usage / 1024 / 1024;
    const memoryLimit = stats.memory_stats.limit / 1024 / 1024 / 1024;
    const memoryPercent = ((memoryUsage / (memoryLimit * 1024)) * 100).toFixed(2);
    const cpuDelta = stats.cpu_stats.cpu_usage.total_usage - (stats.precpu_stats.cpu_usage?.total_usage || 0);
    const systemDelta = stats.cpu_stats.system_cpu_usage - (stats.precpu_stats.system_cpu_usage || 0);
    const cpuPercent = systemDelta > 0 ? ((cpuDelta / systemDelta) * stats.cpu_stats.online_cpus * 100).toFixed(2) : 0;

    return {
      status: info.State.Status,
      memory: { raw: `${memoryUsage.toFixed(2)}MiB / ${memoryLimit.toFixed(2)}GiB`, percent: memoryPercent },
      cpu: cpuPercent
    };
  } catch (error) {
    console.error(`Docker stats error for ${containerName}:`, error.message);
    return { error: `Failed to fetch stats for ${containerName}: ${error.message}` };
  }
}

export async function streamContainerLogs(docker, ws, containerName, client) {
  let isStreaming = true;
  let isStartingStream = false;

  const startLogStream = async () => {
    if (isStartingStream) return false;
    isStartingStream = true;

    try {
      const container = docker.getContainer(containerName);
      const [containers, inspect] = await Promise.all([
        docker.listContainers({ all: true }),
        container.inspect()
      ]);

      if (!containers.some(c => c.Names.includes(`/${containerName}`))) {
        if (isStreaming) ws.send(JSON.stringify({ type: 'docker-logs', error: `Container ${containerName} not found` }));
        return false;
      }

      if (inspect.State.Status !== 'running') {
        if (isStreaming) ws.send(JSON.stringify({ type: 'docker-logs', error: `Container ${containerName} is not running` }));
        return false;
      }

      if (client.logStream) {
        client.logStream.removeAllListeners();
        client.logStream.destroy();
        client.logStream = null;
      }

      const logStream = await container.logs({
        follow: true,
        stdout: true,
        stderr: true,
        tail: parseInt(process.env.LOG_STREAM_TAIL_LINES, 10),
        timestamps: true
      });

      logStream.on('data', (chunk) => {
        if (isStreaming && client.logStream === logStream) {
          ws.send(JSON.stringify({ type: 'docker-logs', data: { log: chunk.toString('utf8') } }));
        }
      });

      logStream.on('error', (error) => {
        if (isStreaming) ws.send(JSON.stringify({ type: 'docker-logs', error: `Log stream error: ${error.message}` }));
      });

      client.logStream = logStream;
      return true;
    } catch (error) {
      if (isStreaming) ws.send(JSON.stringify({ type: 'docker-logs', error: `Failed to stream logs: ${error.message}` }));
      return false;
    } finally {
      isStartingStream = false;
    }
  };

  const monitorContainer = async () => {
    try {
      const container = docker.getContainer(containerName);
      const inspect = await container.inspect();
      if (inspect.State.Status !== 'running') {
        if (client.logStream) {
          client.logStream.removeAllListeners();
          client.logStream.destroy();
          client.logStream = null;
        }
        return false;
      }
      return true;
    } catch (error) {
      return false;
    }
  };

  if (!(await startLogStream())) {
    const monitorInterval = setInterval(async () => {
      if (!isStreaming) return clearInterval(monitorInterval);
      if (await monitorContainer() && !client.logStream && !isStartingStream) {
        await startLogStream();
      }
    }, parseInt(process.env.LOG_STREAM_MONITOR_INTERVAL_MS, 10));

    ws.on('close', () => {
      isStreaming = false;
      clearInterval(monitorInterval);
      if (client.logStream) {
        client.logStream.removeAllListeners();
        client.logStream.destroy();
        client.logStream = null;
      }
    });
    return;
  }

  const monitorInterval = setInterval(async () => {
    if (!isStreaming) return clearInterval(monitorInterval);
    if (await monitorContainer() && !client.logStream && !isStartingStream) {
      await startLogStream();
    }
  }, parseInt(process.env.LOG_STREAM_MONITOR_INTERVAL_MS, 10));

  ws.on('close', () => {
    isStreaming = false;
    clearInterval(monitorInterval);
    if (client.logStream) {
      client.logStream.removeAllListeners();
      client.logStream.destroy();
      client.logStream = null;
    }
  });
}

export async function readServerProperties(docker, containerName) {
  try {
    const container = docker.getContainer(containerName);
    const inspect = await container.inspect();
    if (inspect.State.Status !== 'running') {
      return { error: `Container ${containerName} is not running` };
    }
    const { stdout, stderr } = await execPromise(`docker exec ${containerName} bash -c "cat ${process.env.SERVER_PROPERTIES_PATH}"`);
    if (stderr) return { error: 'Failed to read server.properties' };
    return { content: stdout };
  } catch (error) {
    return { error: `Failed to read server.properties: ${error.message}` };
  }
}

export async function writeServerProperties(docker, containerName, content) {
  try {
    const { randomBytes } = await import('crypto');
    const tmpDir = process.env.TEMP_DIR;
    const randomId = randomBytes(parseInt(process.env.TEMP_FILE_RANDOM_ID_BYTES, 10)).toString('hex');
    const tmpFile = path.join(tmpDir, `server_properties_${randomId}.tmp`);
    const containerFilePath = `${process.env.CONTAINER_TEMP_FILE_PREFIX}${randomId}.tmp`;

    await (await import('fs')).promises.writeFile(tmpFile, content);
    await execPromise(`docker cp ${tmpFile} ${containerName}:${containerFilePath}`);
    await execPromise(`docker exec ${containerName} bash -c "mv ${containerFilePath} ${process.env.SERVER_PROPERTIES_PATH} && chown mc:mc ${process.env.SERVER_PROPERTIES_PATH}"`);
    await (await import('fs')).promises.unlink(tmpFile).catch(err => console.error(`Error deleting temp file: ${err.message}`));
    return { message: 'Server properties updated' };
  } catch (error) {
    return { error: `Failed to write server.properties: ${error.message}` };
  }
}

export async function updateMods(docker, containerName) {
  try {
    const container = docker.getContainer(containerName);
    const inspect = await container.inspect();
    if (inspect.State.Status !== 'running') {
      return { error: `Container ${containerName} is not running` };
    }
    const { stdout, stderr } = await execPromise(`docker exec ${containerName} bash -c 'cd /home/mc/minecraft && mod-manager update'`);
    if (stderr) return { output: stderr };
    return { output: stdout || 'Mod update completed successfully.' };
  } catch (error) {
    return { error: `Failed to update mods: ${error.message}` };
  }
}

export async function createBackup(docker, containerName) {
  try {
    const container = docker.getContainer(containerName);
    const inspect = await container.inspect();
    if (inspect.State.Status !== 'running') {
      return { error: `Container ${containerName} is not running` };
    }
    const command = `docker exec -t ${containerName} bash -c "/home/backup.sh | grep export"`;
    const { stdout, stderr } = await execPromise(command);
    if (stderr) return { error: stderr };

    // Extract the URL using a regular expression
    const urlRegex = /(https:\/\/[^\s]+)/;
    const match = stdout.match(urlRegex);
    if (!match) return { error: 'No download URL found in backup output' };
    
    const downloadURL = match[0];
    return { output: 'Backup completed successfully', downloadURL };
  } catch (error) {
    return { error: `Failed to create backup: ${error.message}` };
  }
}