import pino from 'pino';
import Redis from 'ioredis';

const logger = pino({ name: 'agent-ws' });

/**
 * Agent communication via Redis pub/sub.
 * SaaS publishes approval decisions, agent subscribes.
 * Agent publishes step results, SaaS subscribes.
 *
 * Channel pattern: dd0c:run:{tenantId}:{executionId}:{direction}
 * - direction: 'to_agent' | 'from_agent'
 */
export class AgentBridge {
  private pub: Redis;
  private sub: Redis;

  constructor(redisUrl: string) {
    this.pub = new Redis(redisUrl);
    this.sub = new Redis(redisUrl);
  }

  /**
   * Send approval decision to agent.
   */
  async sendApproval(tenantId: string, executionId: string, stepId: string, decision: 'approve' | 'reject'): Promise<void> {
    const channel = `dd0c:run:${tenantId}:${executionId}:to_agent`;
    const message = JSON.stringify({ type: 'approval', stepId, decision, timestamp: Date.now() });
    await this.pub.publish(channel, message);
    logger.info({ tenantId, executionId, stepId, decision }, 'Approval sent to agent');
  }

  /**
   * Subscribe to step results from agent.
   */
  async onStepResult(
    tenantId: string,
    executionId: string,
    callback: (result: StepResultMessage) => void,
  ): Promise<void> {
    const channel = `dd0c:run:${tenantId}:${executionId}:from_agent`;
    await this.sub.subscribe(channel);

    this.sub.on('message', (ch, message) => {
      if (ch !== channel) return;
      try {
        const parsed = JSON.parse(message) as StepResultMessage;
        callback(parsed);
      } catch (err) {
        logger.warn({ error: (err as Error).message }, 'Invalid message from agent');
      }
    });
  }

  /**
   * Publish step result from agent side.
   */
  async publishStepResult(tenantId: string, executionId: string, result: StepResultMessage): Promise<void> {
    const channel = `dd0c:run:${tenantId}:${executionId}:from_agent`;
    await this.pub.publish(channel, JSON.stringify(result));
  }

  /**
   * Subscribe to approval decisions from SaaS side (agent-side).
   */
  async onApproval(
    tenantId: string,
    executionId: string,
    callback: (decision: { stepId: string; decision: 'approve' | 'reject' }) => void,
  ): Promise<void> {
    const channel = `dd0c:run:${tenantId}:${executionId}:to_agent`;
    await this.sub.subscribe(channel);

    this.sub.on('message', (ch, message) => {
      if (ch !== channel) return;
      try {
        const parsed = JSON.parse(message);
        if (parsed.type === 'approval') {
          callback({ stepId: parsed.stepId, decision: parsed.decision });
        }
      } catch (err) {
        logger.warn({ error: (err as Error).message }, 'Invalid approval message');
      }
    });
  }

  async close(): Promise<void> {
    await this.sub.quit();
    await this.pub.quit();
  }
}

export interface StepResultMessage {
  type: 'step_result';
  stepIndex: number;
  command: string;
  exitCode: number;
  status: 'success' | 'failed' | 'timed_out' | 'rejected' | 'skipped';
  durationMs: number;
  stdoutHash?: string;
  stderrHash?: string;
  timestamp: number;
}
