import * as vscode from 'vscode';
import * as path from 'path';
import * as fs from 'fs';
import * as os from 'os';
import { ChildProcess, spawn } from 'child_process';
import { getMkdocsPath } from './environment';
import { generateMkdocsConfig, restoreOriginalConfig } from './config-generator';
import { resolvePorts, ResolvedPorts } from './port-manager';

export type ServerState = 'stopped' | 'starting' | 'running' | 'stopping';

export interface ServerInfo {
  id: string;
  state: ServerState;
  ports?: ResolvedPorts;
  workspaceDir: string;
  tmpDir?: string;
  startTime?: Date;
}

interface ServerInstance {
  process: ChildProcess;
  info: ServerInfo;
}

type StateChangeListener = () => void;

const servers = new Map<string, ServerInstance>();
const listeners = new Set<StateChangeListener>();

function notify(): void {
  for (const listener of listeners) {
    listener();
  }
}

export function onStateChange(listener: StateChangeListener): vscode.Disposable {
  listeners.add(listener);
  return new vscode.Disposable(() => listeners.delete(listener));
}

export function getAllServers(): ServerInfo[] {
  return Array.from(servers.values()).map((s) => ({ ...s.info }));
}

export function getServerForDir(workspaceDir: string): ServerInfo | undefined {
  const instance = servers.get(workspaceDir);
  return instance ? { ...instance.info } : undefined;
}

export function hasAnyRunning(): boolean {
  return Array.from(servers.values()).some(
    (s) => s.info.state === 'running' || s.info.state === 'starting'
  );
}

export function isRunningForDir(workspaceDir: string): boolean {
  const instance = servers.get(workspaceDir);
  if (!instance) { return false; }
  return instance.info.state === 'running' || instance.info.state === 'starting';
}

export async function startServer(
  workspaceDir: string,
  output: vscode.OutputChannel,
  options?: {
    useUserTheme?: boolean;
    autoGenerated?: boolean;
  }
): Promise<void> {
  if (isRunningForDir(workspaceDir)) {
    vscode.window.showWarningMessage(`Server already running for ${path.basename(workspaceDir)}`);
    return;
  }

  const info: ServerInfo = {
    id: workspaceDir,
    state: 'starting',
    workspaceDir,
  };
  servers.set(workspaceDir, { process: undefined!, info });
  notify();

  const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'mkdocs-wysiwyg-'));

  try {
    const ports = await resolvePorts();

    output.appendLine(`[${path.basename(workspaceDir)}] Generating mkdocs.yml configuration...`);
    generateMkdocsConfig({
      workspaceDir,
      tmpDir,
      websocketPort: ports.websocketPort,
      apiPort: ports.apiPort,
      useUserTheme: options?.useUserTheme ?? false,
      autoGenerated: options?.autoGenerated ?? false,
    });

    const mkdocsPath = getMkdocsPath();
    if (!fs.existsSync(mkdocsPath)) {
      throw new Error(`mkdocs not found at ${mkdocsPath}. Run "MkDocs WYSIWYG: Upgrade Environment" first.`);
    }

    const args = [
      'serve',
      '-f', path.join(workspaceDir, 'mkdocs.yml'),
      '-a', `${ports.host}:${ports.httpPort}`,
      '--livereload',
    ];

    output.appendLine(`[${path.basename(workspaceDir)}] Starting: mkdocs ${args.join(' ')}`);

    const env = {
      ...process.env,
      TMPDIR: tmpDir,
      TMP_DIR: tmpDir,
      TECHDOCS_HOST: ports.host,
      TECHDOCS_PORT: String(ports.httpPort),
      TECHDOCS_WEBSOCKET_PORT: String(ports.websocketPort),
    };

    const proc = spawn(mkdocsPath, args, {
      cwd: workspaceDir,
      env,
      stdio: ['pipe', 'pipe', 'pipe'],
    });

    servers.set(workspaceDir, {
      process: proc,
      info: { id: workspaceDir, state: 'starting', ports, workspaceDir, tmpDir },
    });

    await new Promise<void>((resolve, reject) => {
      const SERVING_RE = /Serving on http/;
      const STARTUP_TIMEOUT_MS = 120_000;
      let settled = false;

      const timer = setTimeout(() => {
        if (!settled) {
          settled = true;
          reject(new Error('MkDocs server did not start within 2 minutes'));
        }
      }, STARTUP_TIMEOUT_MS);

      function onData(data: Buffer): void {
        const text = data.toString();
        output.append(text);
        if (!settled && SERVING_RE.test(text)) {
          settled = true;
          clearTimeout(timer);
          const instance = servers.get(workspaceDir);
          if (instance) {
            instance.info = {
              ...instance.info,
              state: 'running',
              startTime: new Date(),
            };
          }
          notify();
          resolve();
        }
      }

      proc.stdout?.on('data', onData);
      proc.stderr?.on('data', onData);

      proc.on('close', (code) => {
        output.appendLine(`[${path.basename(workspaceDir)}] Server exited with code ${code}`);
        cleanup(workspaceDir, tmpDir, output);
        servers.delete(workspaceDir);
        notify();
        if (!settled) {
          settled = true;
          clearTimeout(timer);
          reject(new Error(`MkDocs server exited with code ${code} before it was ready`));
        }
      });

      proc.on('error', (err) => {
        output.appendLine(`[${path.basename(workspaceDir)}] Server error: ${err.message}`);
        cleanup(workspaceDir, tmpDir, output);
        servers.delete(workspaceDir);
        notify();
        if (!settled) {
          settled = true;
          clearTimeout(timer);
          reject(err);
        }
      });
    });

    output.appendLine(`[${path.basename(workspaceDir)}] Server running at http://${ports.host}:${ports.httpPort}`);
  } catch (err) {
    cleanup(workspaceDir, tmpDir, output);
    servers.delete(workspaceDir);
    notify();
    throw err;
  }
}

export function stopServer(workspaceDir: string, output: vscode.OutputChannel): void {
  const instance = servers.get(workspaceDir);
  if (!instance || instance.info.state === 'stopped') {
    return;
  }

  instance.info = { ...instance.info, state: 'stopping' };
  notify();

  output.appendLine(`[${path.basename(workspaceDir)}] Stopping MkDocs server...`);

  if (process.platform === 'win32') {
    spawn('taskkill', ['/pid', String(instance.process.pid), '/f', '/t']);
  } else {
    instance.process.kill('SIGTERM');
  }
}

export function stopAllServers(output: vscode.OutputChannel): void {
  for (const [dir] of servers) {
    stopServer(dir, output);
  }
}

function cleanup(workspaceDir: string, tmpDir: string, output: vscode.OutputChannel): void {
  try {
    restoreOriginalConfig(workspaceDir, tmpDir);
    output.appendLine(`[${path.basename(workspaceDir)}] Restored original mkdocs.yml`);
  } catch (err) {
    output.appendLine(`[${path.basename(workspaceDir)}] Warning: failed to restore mkdocs.yml: ${err}`);
  }

  try {
    if (fs.existsSync(tmpDir)) {
      fs.rmSync(tmpDir, { recursive: true, force: true });
    }
  } catch {
    // best-effort cleanup
  }
}

export function disposeAllServers(output: vscode.OutputChannel): void {
  stopAllServers(output);
}
