import { createHash, randomBytes } from 'crypto';
import { existsSync, mkdirSync, readFileSync, unlinkSync } from 'fs';
import { tmpdir } from 'os';
import { join } from 'path';
import { connect, createServer, type Server } from 'net';

export const GAME_START_RUNTIME_FILE = 'game-start.json';
export const OWNER_CONTROL_TIMEOUT_MS = 2000;

export type OwnerControlType = 'stop' | 'quit' | 'leave' | 'switch_strategy' | 'stop_strategy' | 'snapshot';

export interface OwnerControlInfo {
  kind: 'node-net-socket';
  path: string;
}

export interface OwnerControlRequest {
  token?: string;
  type?: OwnerControlType;
  strategy?: string;
  args?: string[];
}

export interface OwnerControlResponse {
  ok: boolean;
  type?: string;
  error?: string;
  message?: string;
  [key: string]: any;
}

export interface OwnerControlServer {
  control: OwnerControlInfo;
  token: string;
  close: () => Promise<void>;
}

export function gameStartRuntimePath(stateDir: string): string {
  return join(stateDir, GAME_START_RUNTIME_FILE);
}

export function readGameStartRuntime(stateDir: string): Record<string, any> | null {
  const runtimePath = gameStartRuntimePath(stateDir);
  if (!existsSync(runtimePath)) return null;
  try {
    return JSON.parse(readFileSync(runtimePath, 'utf8'));
  } catch {}
  return null;
}

function ownerControlPath(stateDir: string, pid: number): string {
  const hash = createHash('sha1').update(stateDir).digest('hex').slice(0, 12);
  if (process.platform === 'win32') return `\\\\.\\pipe\\clawclaw-${hash}-${pid}`;
  const dir = join(tmpdir(), 'clawclaw');
  mkdirSync(dir, { recursive: true });
  return join(dir, `${hash}-${pid}.sock`);
}

export async function startOwnerControlServer(
  stateDir: string,
  onRequest: (request: OwnerControlRequest) => Promise<OwnerControlResponse> | OwnerControlResponse,
): Promise<OwnerControlServer> {
  const token = randomBytes(24).toString('hex');
  const path = ownerControlPath(stateDir, process.pid);
  if (process.platform !== 'win32') {
    try { unlinkSync(path); } catch {}
  }
  const server: Server = createServer((socket) => {
    let buffer = '';
    socket.setEncoding('utf8');
    socket.on('data', (chunk) => {
      buffer += chunk;
      const nl = buffer.indexOf('\n');
      if (nl < 0) return;
      const line = buffer.slice(0, nl);
      buffer = buffer.slice(nl + 1);
      void (async () => {
        try {
          const request = JSON.parse(line) as OwnerControlRequest;
          if (request.token !== token) {
            socket.end(JSON.stringify({ ok: false, error: 'invalid_token' }) + '\n');
            return;
          }
          const response = await onRequest(request);
          socket.end(JSON.stringify(response) + '\n');
        } catch (err: any) {
          socket.end(JSON.stringify({ ok: false, error: err?.message ?? String(err) }) + '\n');
        }
      })();
    });
  });
  await new Promise<void>((resolve, reject) => {
    server.once('error', reject);
    server.listen(path, () => {
      server.off('error', reject);
      resolve();
    });
  });
  return {
    control: { kind: 'node-net-socket', path },
    token,
    close: () => new Promise((resolve) => {
      server.close(() => resolve());
    }),
  };
}

export function sendOwnerControlRequest(
  stateDir: string,
  type: OwnerControlType,
  payload: Record<string, any> = {},
  timeoutMs = OWNER_CONTROL_TIMEOUT_MS,
): Promise<OwnerControlResponse | null> {
  const info = readGameStartRuntime(stateDir);
  const path = typeof info?.control?.path === 'string' ? info.control.path : '';
  const token = typeof info?.control_token === 'string' ? info.control_token : '';
  if (!path || !token) return Promise.resolve(null);
  return new Promise((resolve, reject) => {
    const socket = connect(path);
    let buffer = '';
    const timer = setTimeout(() => {
      socket.destroy();
      reject(new Error('owner_control_timeout'));
    }, timeoutMs);
    socket.setEncoding('utf8');
    socket.on('connect', () => {
      socket.write(JSON.stringify({ token, type, ...payload }) + '\n');
    });
    socket.on('data', (chunk) => {
      buffer += chunk;
      const nl = buffer.indexOf('\n');
      if (nl < 0) return;
      clearTimeout(timer);
      socket.end();
      try {
        resolve(JSON.parse(buffer.slice(0, nl)));
      } catch (err) {
        reject(err);
      }
    });
    socket.on('error', (err) => {
      clearTimeout(timer);
      reject(err);
    });
    socket.on('close', () => {
      clearTimeout(timer);
      if (!buffer) resolve(null);
    });
  });
}
