import { randomUUID } from 'node:crypto';

import {
  GatewayTimeoutException,
  Inject,
  Injectable,
  ServiceUnavailableException,
} from '@nestjs/common';
import Redis from 'ioredis';

import { getConfig } from '@hexahost/config';

import { REDIS_CLIENT, ServersService } from '../servers/servers.service';

export interface AgentResponse {
  success: boolean;
  result?: unknown;
  resultCode?: string;
  errorCode?: string;
  errorMessage?: string;
}

@Injectable()
export class NodeBridgeService {
  constructor(
    @Inject(REDIS_CLIENT) private readonly redis: Redis,
    private readonly serversService: ServersService,
  ) {}

  async sendAgentRequest(
    nodeId: string,
    type: string,
    payload: Record<string, unknown>,
    timeoutMs = 30_000,
  ): Promise<AgentResponse> {
    const messageId = randomUUID();

    const config = getConfig();
    const subscriber = new Redis(config.REDIS_URL, {
      maxRetriesPerRequest: null,
    });

    const responseChannel = `hgc:node:responses:${messageId}`;

    try {
      const responsePromise = new Promise<AgentResponse>((resolve, reject) => {
        const timer = setTimeout(() => {
          reject(new GatewayTimeoutException('Agent request timed out'));
        }, timeoutMs);

        subscriber.on('message', (channel, message) => {
          if (channel !== responseChannel) {
            return;
          }

          clearTimeout(timer);

          try {
            resolve(JSON.parse(message) as AgentResponse);
          } catch {
            reject(new ServiceUnavailableException('Invalid agent response'));
          }
        });
      });

      await subscriber.subscribe(responseChannel);

      await this.serversService.publishNodeCommand(nodeId, {
        protocolVersion: 1,
        messageId,
        type,
        timestamp: new Date().toISOString(),
        payload: {
          ...payload,
          generation: payload['generation'] ?? 0,
        },
      });

      return await responsePromise;
    } finally {
      await subscriber.unsubscribe(responseChannel);
      await subscriber.quit();
    }
  }
}
