import type { Command } from 'commander';
import { hostname } from 'node:os';
import { randomUUID } from 'node:crypto';
import { CompanionRuntimeClient } from '../companion/index.js';
import type { SetNodeStatusInput } from '../companion/index.js';
import { getConfigPath, loadConfigSafe } from './shared.js';

type CompanionPlatform = SetNodeStatusInput['platform'];

interface CompanionCommandOptions {
  config?: string;
  url?: string;
  token?: string;
  nodeId?: string;
  role?: string;
  platform?: CompanionPlatform;
  capability?: string[];
  heartbeat?: string;
  once?: boolean;
}

function resolveGatewayUrl(options: CompanionCommandOptions, configPath: string): string {
  if (options.url && options.url.trim().length > 0) {
    return options.url.trim();
  }

  const loaded = loadConfigSafe(configPath);
  if (loaded.config) {
    const port = loaded.config.server.port;
    return `ws://127.0.0.1:${port}`;
  }

  return 'ws://127.0.0.1:18800';
}

function resolveGatewayToken(options: CompanionCommandOptions, configPath: string): string | undefined {
  if (options.token && options.token.trim().length > 0) {
    return options.token.trim();
  }

  const loaded = loadConfigSafe(configPath);
  return loaded.config?.server.token;
}

function resolveCapabilities(platform: CompanionPlatform, provided?: string[]): string[] {
  if (provided && provided.length > 0) {
    return provided.map((v) => v.trim()).filter(Boolean);
  }

  if (platform === 'ios' || platform === 'macos') {
    return ['ui.canvas', 'node.status.write', 'node.location.write', 'node.push.register'];
  }
  if (platform === 'android') {
    return ['ui.canvas', 'node.status.write', 'node.location.write', 'node.push.register'];
  }
  return ['ui.canvas', 'node.status.write'];
}

function resolveNodeId(options: CompanionCommandOptions, platform: CompanionPlatform): string {
  if (options.nodeId && options.nodeId.trim().length > 0) {
    return options.nodeId.trim();
  }
  return `${platform}-${hostname()}-${randomUUID().slice(0, 8)}`;
}

function parseHeartbeatSeconds(value: string | undefined): number {
  const raw = value ?? '30';
  const parsed = Number.parseInt(raw, 10);
  if (!Number.isFinite(parsed) || parsed <= 0 || parsed > 86_400) {
    throw new Error('heartbeat must be an integer between 1 and 86400 seconds');
  }
  return parsed;
}

async function publishHeartbeat(
  runtime: CompanionRuntimeClient,
  platform: CompanionPlatform,
): Promise<void> {
  await runtime.setNodeStatus({
    platform,
    statusText: 'heartbeat',
    powerSource: 'unknown',
  });
}

export async function runCompanionSession(options: CompanionCommandOptions): Promise<void> {
  const configPath = options.config ?? getConfigPath();
  const platform: CompanionPlatform = options.platform ?? 'macos';
  const gatewayUrl = resolveGatewayUrl(options, configPath);
  const gatewayToken = resolveGatewayToken(options, configPath);
  const role = options.role?.trim() || 'companion';
  const nodeId = resolveNodeId(options, platform);
  const capabilities = resolveCapabilities(platform, options.capability);
  const heartbeatSeconds = parseHeartbeatSeconds(options.heartbeat);

  const runtime = new CompanionRuntimeClient({
    url: gatewayUrl,
    token: gatewayToken,
  });

  const stopSignals: NodeJS.Signals[] = ['SIGINT', 'SIGTERM'];
  let heartbeatTimer: NodeJS.Timeout | null = null;

  const cleanup = (): void => {
    if (heartbeatTimer) {
      clearInterval(heartbeatTimer);
      heartbeatTimer = null;
    }
    runtime.disconnect(1000, 'Companion shutting down');
  };

  for (const signal of stopSignals) {
    process.once(signal, cleanup);
  }

  runtime.subscribeAgentStream((data) => {
    const payload = data as { sessionId?: string; content?: string };
    const session = payload.sessionId ? ` (${payload.sessionId})` : '';
    if (payload.content) {
      console.log(`[agent.stream${session}] ${payload.content}`);
    }
  });

  runtime.subscribeAgentTyping((data) => {
    const payload = data as { sessionId?: string; phase?: string };
    const session = payload.sessionId ? ` (${payload.sessionId})` : '';
    const phase = payload.phase ?? 'typing';
    console.log(`[agent.typing${session}] ${phase}`);
  });

  try {
    await runtime.connect();
    const register = await runtime.registerNode({
      nodeId,
      role,
      capabilities,
    });

    await publishHeartbeat(runtime, platform);

    console.log(`Connected companion node ${register.node.id} (${platform}, role=${role})`);
    console.log(`Gateway: ${gatewayUrl}`);
    console.log(`Capabilities: ${capabilities.join(', ') || '(none)'}`);

    if (options.once) {
      cleanup();
      return;
    }

    heartbeatTimer = setInterval(() => {
      void publishHeartbeat(runtime, platform).catch((error: unknown) => {
        const message = error instanceof Error ? error.message : String(error);
        console.error(`Heartbeat failed: ${message}`);
      });
    }, heartbeatSeconds * 1000);

    await new Promise<void>(() => {
      // Keep process alive until interrupted.
    });
  } catch (error) {
    cleanup();
    throw error;
  }
}

export function registerCompanionCommand(program: Command): void {
  program
    .command('companion')
    .description('Run a minimal gateway companion node client')
    .option('-c, --config <path>', 'Config file path')
    .option('--url <url>', 'Gateway WebSocket URL (default from config server.port)')
    .option('--token <token>', 'Gateway auth token (default from config server.token)')
    .option('--node-id <id>', 'Node ID to register (default: generated)')
    .option('--role <role>', 'Node role', 'companion')
    .option('--platform <platform>', 'Node platform (macos|ios|android|linux|windows|unknown)', 'macos')
    .option('--capability <name...>', 'Capability list override')
    .option('--heartbeat <seconds>', 'Heartbeat interval in seconds', '30')
    .option('--once', 'Connect, register, publish one heartbeat, then exit', false)
    .action(async (opts: CompanionCommandOptions) => {
      try {
        await runCompanionSession(opts);
      } catch (error) {
        const message = error instanceof Error ? error.message : String(error);
        console.error(`Companion command failed: ${message}`);
        process.exitCode = 1;
      }
    });
}

