import { Inject, Injectable, Logger } from "@nestjs/common";
import { PrismaService } from "../prisma/prisma.service";
import { CHAT_PROVIDERS } from "../bridge/bridge.constants";
import type { IChatProvider } from "../bridge/interfaces/chat-provider.interface";
import {
  JOB_CREATED,
  JOB_STARTED,
  JOB_COMPLETED,
  JOB_FAILED,
  JOB_CANCELLED,
  STEP_STARTED,
  STEP_COMPLETED,
  STEP_FAILED,
  GATE_PASSED,
  GATE_FAILED,
} from "../job-events/event-types";

/**
 * Herald Service - Status broadcasting and notifications
 *
 * Responsibilities:
 * - Subscribe to job events
 * - Format status messages with PDA-friendly language
 * - Route to appropriate channels based on workspace config
 * - Broadcast to ALL active chat providers (Discord, Matrix, etc.)
 */
@Injectable()
export class HeraldService {
  private readonly logger = new Logger(HeraldService.name);

  constructor(
    private readonly prisma: PrismaService,
    @Inject(CHAT_PROVIDERS) private readonly chatProviders: IChatProvider[]
  ) {}

  /**
   * Broadcast a job event to all connected chat providers
   */
  async broadcastJobEvent(
    jobId: string,
    event: {
      id: string;
      jobId: string;
      stepId?: string | null;
      type: string;
      timestamp: Date;
      actor: string;
      payload: unknown;
    }
  ): Promise<void> {
    // Get job details
    const job = await this.prisma.runnerJob.findUnique({
      where: { id: jobId },
      select: {
        id: true,
        workspaceId: true,
        type: true,
      },
    });

    if (!job) {
      this.logger.warn(`Job ${jobId} not found, skipping broadcast`);
      return;
    }

    // Get threadId from first event payload (job.created event has metadata)
    const firstEvent = await this.prisma.jobEvent.findFirst({
      where: {
        jobId,
        type: JOB_CREATED,
      },
      select: {
        payload: true,
      },
    });

    const firstEventPayload = firstEvent?.payload as Record<string, unknown> | undefined;
    const metadata = firstEventPayload?.metadata as Record<string, unknown> | undefined;
    const threadId = metadata?.threadId as string | undefined;
    const channelId = metadata?.channelId as string | undefined;

    if (!threadId) {
      this.logger.debug(`Job ${jobId} has no threadId, skipping broadcast`);
      return;
    }

    // Format message
    const message = this.formatJobEventMessage(event, job, metadata);

    // Broadcast to all connected providers
    for (const provider of this.chatProviders) {
      if (!provider.isConnected()) {
        continue;
      }

      try {
        await provider.sendThreadMessage({
          threadId,
          channelId: channelId ?? "",
          content: message,
        });
      } catch (error: unknown) {
        // Log and continue — one provider failure must not block others
        const providerName = provider.constructor.name;
        this.logger.error(
          `Failed to broadcast event ${event.type} for job ${jobId} via ${providerName}:`,
          error instanceof Error ? error.message : error
        );
      }
    }

    this.logger.debug(`Broadcasted event ${event.type} for job ${jobId} to thread ${threadId}`);
  }

  /**
   * Format a job event into a PDA-friendly message
   */
  formatJobEventMessage(
    event: {
      id: string;
      jobId: string;
      stepId?: string | null;
      type: string;
      timestamp: Date;
      actor: string;
      payload: unknown;
    },
    _job: {
      id: string;
      type: string;
    },
    metadata?: Record<string, unknown>
  ): string {
    const payload = event.payload as Record<string, unknown>;
    const issueNumber = metadata?.issueNumber as number | undefined;

    switch (event.type) {
      case JOB_CREATED:
        return this.formatJobCreated(issueNumber, payload);

      case JOB_STARTED:
        return this.formatJobStarted(issueNumber, payload);

      case JOB_COMPLETED:
        return this.formatJobCompleted(issueNumber, payload);

      case JOB_FAILED:
        return this.formatJobFailed(issueNumber, payload);

      case JOB_CANCELLED:
        return this.formatJobCancelled(issueNumber, payload);

      case STEP_STARTED:
        return this.formatStepStarted(issueNumber, payload);

      case STEP_COMPLETED:
        return this.formatStepCompleted(issueNumber, payload);

      case STEP_FAILED:
        return this.formatStepFailed(issueNumber, payload);

      case GATE_PASSED:
        return this.formatGatePassed(issueNumber, payload);

      case GATE_FAILED:
        return this.formatGateFailed(issueNumber, payload);

      default:
        return `Event: ${event.type}`;
    }
  }

  /**
   * Get the channel ID for a job type from workspace settings
   */
  async getChannelForJobType(workspaceId: string, jobType: string): Promise<string | null> {
    const workspace = await this.prisma.workspace.findUnique({
      where: { id: workspaceId },
      select: { settings: true },
    });

    if (!workspace) {
      return null;
    }

    const settings = workspace.settings as Record<string, unknown>;
    const heraldSettings = settings.herald as Record<string, unknown> | undefined;
    const channelMappings = heraldSettings?.channelMappings as Record<string, string> | undefined;
    const defaultChannel = heraldSettings?.defaultChannel as string | undefined;

    // Try to get channel for job type
    if (channelMappings?.[jobType]) {
      return channelMappings[jobType];
    }

    // Fall back to default channel
    if (defaultChannel) {
      return defaultChannel;
    }

    return null;
  }

  // Message formatting methods with PDA-friendly language

  private formatJobCreated(
    issueNumber: number | undefined,
    _payload: Record<string, unknown>
  ): string {
    const issue = issueNumber ? `#${String(issueNumber)}` : "task";
    return `🟢 Job created for ${issue}`;
  }

  private formatJobStarted(
    issueNumber: number | undefined,
    _payload: Record<string, unknown>
  ): string {
    const issue = issueNumber ? `#${String(issueNumber)}` : "task";
    return `🔵 Job started for ${issue}`;
  }

  private formatJobCompleted(
    issueNumber: number | undefined,
    payload: Record<string, unknown>
  ): string {
    const issue = issueNumber ? `#${String(issueNumber)}` : "task";
    const duration = payload.duration as number | undefined;
    const durationText = duration ? ` (${String(duration)}s)` : "";
    return `✅ Job completed for ${issue}${durationText}`;
  }

  private formatJobFailed(
    issueNumber: number | undefined,
    payload: Record<string, unknown>
  ): string {
    const issue = issueNumber ? `#${String(issueNumber)}` : "task";
    const error = payload.error as string | undefined;
    const errorText = error ? `\n${error}` : "";
    return `⚠️ Job encountered an issue for ${issue}${errorText}`;
  }

  private formatJobCancelled(
    issueNumber: number | undefined,
    _payload: Record<string, unknown>
  ): string {
    const issue = issueNumber ? `#${String(issueNumber)}` : "task";
    return `⏸️ Job paused for ${issue}`;
  }

  private formatStepStarted(
    _issueNumber: number | undefined,
    payload: Record<string, unknown>
  ): string {
    const stepName = payload.stepName as string | undefined;
    return `▶️ Step started: ${stepName ?? "unknown"}`;
  }

  private formatStepCompleted(
    _issueNumber: number | undefined,
    payload: Record<string, unknown>
  ): string {
    const stepName = payload.stepName as string | undefined;
    return `✅ Step completed: ${stepName ?? "unknown"}`;
  }

  private formatStepFailed(
    _issueNumber: number | undefined,
    payload: Record<string, unknown>
  ): string {
    const stepName = payload.stepName as string | undefined;
    const error = payload.error as string | undefined;
    const errorText = error ? `\n${error}` : "";
    return `⚠️ Step needs attention: ${stepName ?? "unknown"}${errorText}`;
  }

  private formatGatePassed(
    _issueNumber: number | undefined,
    payload: Record<string, unknown>
  ): string {
    const gateName = payload.gateName as string | undefined;
    return `✅ Gate passed: ${gateName ?? "unknown"}`;
  }

  private formatGateFailed(
    _issueNumber: number | undefined,
    payload: Record<string, unknown>
  ): string {
    const gateName = payload.gateName as string | undefined;
    const error = payload.error as string | undefined;
    const errorText = error ? `\n${error}` : "";
    return `⚠️ Gate needs attention: ${gateName ?? "unknown"}${errorText}`;
  }
}
