/**
 * Redis publisher for Sentinel events
 * Publishes guard/audit events to Redis pub/sub channels
 */

import { getRedisClient } from '../db/redis.js';
import { createLogger } from '@4runr/shared';
import {
  buildSentinelFailureOutput,
  parsePolicyFromKillReason,
} from '../security/sentinel-run-failure.js';
import {
  sentinelKillsTotal,
  sentinelPolicyViolationsTotal,
} from '../metrics/index.js';

const logger = createLogger('Gateway:Sentinel:RedisPublisher');

export interface SentinelEvent {
  type: 'shield.decision' | 'judge.verdict' | 'audit.log' | 'policy.violation' | 'kill';
  runId: string;
  timestamp: number;
  data: Record<string, unknown>;
}

export interface PolicyViolationEventData {
  policy: string;
  reason: string;
  threshold: number;
  actual: number;
  action: 'deny';
}

export type SentinelKillTrigger = 'policy' | 'manual' | 'operator';

/**
 * Publish a Sentinel event to Redis
 */
export async function publishSentinelEvent(event: SentinelEvent): Promise<void> {
  try {
    const redisClient = await getRedisClient();
    if (!redisClient) {
      return;
    }

    const channel = `sentinel:${event.type}`;
    const message = JSON.stringify(event);

    await redisClient.publish(channel, message);
    logger.debug('Published Sentinel event to Redis', {
      channel,
      runId: event.runId,
      type: event.type,
    });
  } catch (error) {
    logger.error('Failed to publish Sentinel event to Redis', {
      runId: event.runId,
      type: event.type,
      error: error instanceof Error ? error.message : String(error),
    });
  }
}

export function buildPolicyViolationEvent(
  runId: string,
  violation: PolicyViolationEventData,
  timestamp = Date.now()
): SentinelEvent {
  return {
    type: 'policy.violation',
    runId,
    timestamp,
    data: { ...violation },
  };
}

export function buildKillEvent(
  runId: string,
  reason: string,
  trigger: SentinelKillTrigger,
  timestamp = Date.now()
): SentinelEvent {
  return {
    type: 'kill',
    runId,
    timestamp,
    data: {
      reason,
      trigger,
      source: 'sentinel',
      output: buildSentinelFailureOutput(reason),
      policy: parsePolicyFromKillReason(reason),
    },
  };
}

export function buildAuditLogEvent(
  runId: string,
  action: 'kill' | 'policy_violation',
  payload: Record<string, unknown>,
  timestamp = Date.now()
): SentinelEvent {
  return {
    type: 'audit.log',
    runId,
    timestamp,
    data: { action, ...payload },
  };
}

function resolveKillTrigger(reason: string): SentinelKillTrigger {
  if (reason === 'manual_cancellation') {
    return 'manual';
  }
  if (reason.startsWith('policy_violation:')) {
    return 'policy';
  }
  return 'operator';
}

/**
 * Publish kill (+ audit) after run store is updated. Policy violations are
 * published separately via setPolicyViolationPublisher when evaluation fires.
 */
export async function publishSentinelRunKill(
  runId: string,
  reason: string
): Promise<void> {
  const trigger = resolveKillTrigger(reason);
  sentinelKillsTotal.inc({ trigger });

  await publishSentinelEvent(buildKillEvent(runId, reason, trigger));
  await publishSentinelEvent(
    buildAuditLogEvent(runId, 'kill', {
      reason,
      trigger,
      output: buildSentinelFailureOutput(reason),
    })
  );
}

/**
 * Policy violation observed during Sentinel evaluation (before kill completes).
 */
export async function publishSentinelPolicyViolation(
  runId: string,
  violation: PolicyViolationEventData
): Promise<void> {
  sentinelPolicyViolationsTotal.inc({ policy: violation.policy });

  await publishSentinelEvent(buildPolicyViolationEvent(runId, violation));
  await publishSentinelEvent(
    buildAuditLogEvent(runId, 'policy_violation', {
      policy: violation.policy,
      reason: violation.reason,
      threshold: violation.threshold,
      actual: violation.actual,
      action: 'deny',
    })
  );
}

/** Fire-and-forget wrapper for hot paths (Shield pattern). */
export function publishSentinelRunKillFireAndForget(
  runId: string,
  reason: string
): void {
  void publishSentinelRunKill(runId, reason);
}

export function publishSentinelPolicyViolationFireAndForget(
  runId: string,
  violation: PolicyViolationEventData
): void {
  void publishSentinelPolicyViolation(runId, violation);
}
