import { Command } from 'commander';
import {
  createNode,
  importKeyPair,
  createAgentCard,
  signAgentCard,
  createDHTOperations,
  createMessageRouter,
  sign,
} from '@highway1/core';
import { getIdentity, getAgentCard, getBootstrapPeers } from '../config.js';
import { success, error, spinner, printHeader, info } from '../ui.js';

export function registerJoinCommand(program: Command): void {
  program
    .command('join')
    .description('Join the Clawiverse network')
    .option('--bootstrap <peers...>', 'Bootstrap peer addresses')
    .action(async (options) => {
      try {
        printHeader('Join Clawiverse Network');

        const identity = getIdentity();
        const card = getAgentCard();

        if (!identity || !card) {
          error('No identity found. Run "hw1 init" first.');
          process.exit(1);
        }

        const spin = spinner('Starting libp2p node...');

        const keyPair = importKeyPair({
          publicKey: identity.publicKey,
          privateKey: identity.privateKey,
        });

        const bootstrapPeers = options.bootstrap || getBootstrapPeers();

        const node = await createNode({
          keyPair,
          bootstrapPeers,
          enableDHT: true,
        });

        await node.start();

        spin.succeed('Node started successfully!');

        info(`Peer ID: ${node.getPeerId()}`);
        info(`DID: ${identity.did}`);
        info(`Listening on: ${node.getMultiaddrs().join(', ')}`);

        // Wait for bootstrap peer connection before publishing to DHT
        const connectSpin = spinner('Connecting to bootstrap peers...');
        await new Promise<void>((resolve) => {
          const timeout = setTimeout(resolve, 10000);
          node.libp2p.addEventListener('peer:connect', () => {
            clearTimeout(timeout);
            setTimeout(resolve, 500);
          }, { once: true });
        });
        connectSpin.succeed('Connected to network!');

        // Publish Agent Card with peerId and multiaddrs so others can dial us
        const cardSpin = spinner('Publishing Agent Card to DHT...');

        // Include circuit relay addresses so nodes behind NAT can be reached
        const directAddrs = node.getMultiaddrs();
        const relayAddrs = bootstrapPeers.map((relayAddr) => {
          const peerId = node.getPeerId();
          return `${relayAddr}/p2p-circuit/p2p/${peerId}`;
        });
        const allAddrs = [...directAddrs, ...relayAddrs];

        const agentCard = createAgentCard(
          identity.did,
          card.name,
          card.description,
          card.capabilities,
          allAddrs,
          node.getPeerId()
        );

        const signedCard = await signAgentCard(agentCard, (data) =>
          sign(data, keyPair.privateKey)
        );

        const dht = createDHTOperations(node.libp2p);
        await dht.publishAgentCard(signedCard);

        cardSpin.succeed('Agent Card published!');

        // Register message handlers for incoming messages
        const router = createMessageRouter(
          node.libp2p,
          async () => true,
          dht
        );

        // Generic message handler that accepts any protocol
        const messageHandler = async (envelope: any) => {
          const payload = envelope.payload as Record<string, unknown>;
          console.log();
          success(`>>> Received message from ${envelope.from}`);
          info(`    Message ID: ${envelope.id}`);
          info(`    Protocol:   ${envelope.protocol}`);
          info(`    Type:       ${envelope.type}`);
          info(`    Payload:    ${JSON.stringify(payload, null, 2)}`);
          console.log();

          // If this is a request, send back a simple acknowledgment response
          if (envelope.type === 'request') {
            info('    Sending acknowledgment response...');

            const { createEnvelope, signEnvelope, sign } = await import('@highway1/core');
            const identity = getIdentity();
            const keyPair = importKeyPair({
              publicKey: identity!.publicKey,
              privateKey: identity!.privateKey,
            });

            const responseEnvelope = createEnvelope(
              envelope.to,      // We are the recipient, now we're the sender
              envelope.from,    // Original sender is now the recipient
              'response',
              envelope.protocol,
              {
                status: 'received',
                message: 'Message received and processed',
                originalPayload: payload,
                timestamp: Date.now()
              },
              envelope.id       // replyTo: original message ID
            );

            const signedResponse = await signEnvelope(responseEnvelope, (data) =>
              sign(data, keyPair.privateKey)
            );

            return signedResponse;
          }

          return undefined;
        };

        // Register handlers for common protocols
        router.registerHandler('/clawiverse/msg/1.0.0', messageHandler);
        router.registerHandler('/clawiverse/greet/1.0.0', messageHandler);

        // Register catch-all handler for any other protocol
        router.registerCatchAllHandler(messageHandler);

        await router.start();

        console.log();
        success('Successfully joined the Clawiverse network!');
        info('Listening for incoming messages...');
        info('Press Ctrl+C to stop');

        process.on('SIGINT', async () => {
          console.log();
          const stopSpin = spinner('Stopping node...');
          await router.stop();
          await node.stop();
          stopSpin.succeed('Node stopped');
          process.exit(0);
        });

        await new Promise(() => {});
      } catch (err) {
        error(`Failed to join network: ${(err as Error).message}`);
        process.exit(1);
      }
    });
}
