import { Injectable, Logger } from "@nestjs/common";
import { Client, Events, GatewayIntentBits, TextChannel, ThreadChannel } from "discord.js";
import { StitcherService } from "../../stitcher/stitcher.service";
import { sanitizeForLogging } from "../../common/utils";
import type {
  IChatProvider,
  ChatMessage,
  ChatCommand,
  ThreadCreateOptions,
  ThreadMessageOptions,
} from "../interfaces";

/**
 * Discord Service - Discord chat platform integration
 *
 * Responsibilities:
 * - Connect to Discord via bot token
 * - Listen for commands in designated channels
 * - Forward commands to stitcher
 * - Receive status updates from herald
 * - Post updates to threads
 */
@Injectable()
export class DiscordService implements IChatProvider {
  private readonly logger = new Logger(DiscordService.name);
  private client: Client;
  private connected = false;
  private readonly botToken: string;
  private readonly controlChannelId: string;
  private readonly workspaceId: string;

  constructor(private readonly stitcherService: StitcherService) {
    this.botToken = process.env.DISCORD_BOT_TOKEN ?? "";
    this.controlChannelId = process.env.DISCORD_CONTROL_CHANNEL_ID ?? "";
    this.workspaceId = process.env.DISCORD_WORKSPACE_ID ?? "";

    // Initialize Discord client with required intents
    this.client = new Client({
      intents: [
        GatewayIntentBits.Guilds,
        GatewayIntentBits.GuildMessages,
        GatewayIntentBits.MessageContent,
      ],
    });

    this.setupEventHandlers();
  }

  /**
   * Setup event handlers for Discord client
   */
  private setupEventHandlers(): void {
    this.client.once(Events.ClientReady, () => {
      this.connected = true;
      const userTag = this.client.user?.tag ?? "Unknown";
      this.logger.log(`Discord bot connected as ${userTag}`);
    });

    this.client.on(Events.MessageCreate, (message) => {
      // Ignore bot messages
      if (message.author.bot) return;

      // Check if message is in control channel
      if (message.channelId !== this.controlChannelId) return;

      // Parse message into ChatMessage format
      const chatMessage: ChatMessage = {
        id: message.id,
        channelId: message.channelId,
        authorId: message.author.id,
        authorName: message.author.username,
        content: message.content,
        timestamp: message.createdAt,
        ...(message.channel.isThread() && { threadId: message.channelId }),
      };

      // Parse command
      const command = this.parseCommand(chatMessage);
      if (command) {
        void this.handleCommand(command);
      }
    });

    this.client.on(Events.Error, (error: Error) => {
      // Sanitize error before logging to prevent secret exposure
      const sanitizedError = sanitizeForLogging(error);
      this.logger.error("Discord client error:", sanitizedError);
    });
  }

  /**
   * Connect to Discord
   */
  async connect(): Promise<void> {
    if (!this.botToken) {
      throw new Error("DISCORD_BOT_TOKEN is required");
    }

    if (!this.workspaceId) {
      throw new Error("DISCORD_WORKSPACE_ID is required");
    }

    this.logger.log("Connecting to Discord...");
    await this.client.login(this.botToken);
  }

  /**
   * Disconnect from Discord
   */
  async disconnect(): Promise<void> {
    this.logger.log("Disconnecting from Discord...");
    this.connected = false;
    await this.client.destroy();
  }

  /**
   * Check if the provider is connected
   */
  isConnected(): boolean {
    return this.connected;
  }

  /**
   * Send a message to a channel or thread
   */
  async sendMessage(channelId: string, content: string): Promise<void> {
    const channel = await this.client.channels.fetch(channelId);

    if (!channel) {
      throw new Error("Channel not found");
    }

    if (channel.isTextBased()) {
      await (channel as TextChannel).send(content);
    } else {
      throw new Error("Channel is not text-based");
    }
  }

  /**
   * Create a thread for job updates
   */
  async createThread(options: ThreadCreateOptions): Promise<string> {
    const { channelId, name, message } = options;

    const channel = await this.client.channels.fetch(channelId);

    if (!channel) {
      throw new Error("Channel not found");
    }

    if (!channel.isTextBased()) {
      throw new Error("Channel does not support threads");
    }

    const thread = await (channel as TextChannel).threads.create({
      name,
      reason: "Job updates thread",
    });

    // Send initial message to thread
    await thread.send(message);

    return thread.id;
  }

  /**
   * Send a message to a thread
   */
  async sendThreadMessage(options: ThreadMessageOptions): Promise<void> {
    const { threadId, content } = options;

    const thread = await this.client.channels.fetch(threadId);

    if (!thread) {
      throw new Error("Thread not found");
    }

    if (thread.isThread()) {
      await (thread as ThreadChannel).send(content);
    } else {
      throw new Error("Channel is not a thread");
    }
  }

  /**
   * Parse a command from a message
   */
  parseCommand(message: ChatMessage): ChatCommand | null {
    const { content } = message;

    // Check if message mentions @mosaic
    if (!content.toLowerCase().includes("@mosaic")) {
      return null;
    }

    // Extract command and arguments
    const parts = content.trim().split(/\s+/);
    const mosaicIndex = parts.findIndex((part) => part.toLowerCase().includes("@mosaic"));

    if (mosaicIndex === -1 || mosaicIndex === parts.length - 1) {
      return null;
    }

    const commandPart = parts[mosaicIndex + 1];
    if (!commandPart) {
      return null;
    }

    const command = commandPart.toLowerCase();
    const args = parts.slice(mosaicIndex + 2);

    // Valid commands
    const validCommands = ["fix", "status", "cancel", "verbose", "quiet", "help"];

    if (!validCommands.includes(command)) {
      return null;
    }

    return {
      command,
      args,
      message,
    };
  }

  /**
   * Handle a parsed command
   */
  async handleCommand(command: ChatCommand): Promise<void> {
    const { command: cmd, args, message } = command;

    this.logger.log(
      `Handling command: ${cmd} with args: ${args.join(", ")} from ${message.authorName}`
    );

    switch (cmd) {
      case "fix":
        await this.handleFixCommand(args, message);
        break;
      case "status":
        await this.handleStatusCommand(args, message);
        break;
      case "cancel":
        await this.handleCancelCommand(args, message);
        break;
      case "verbose":
        await this.handleVerboseCommand(args, message);
        break;
      case "quiet":
        await this.handleQuietCommand(args, message);
        break;
      case "help":
        await this.handleHelpCommand(args, message);
        break;
      default:
        await this.sendMessage(
          message.channelId,
          `Unknown command: ${cmd}. Type \`@mosaic help\` for available commands.`
        );
    }
  }

  /**
   * Handle fix command - Start a job for an issue
   */
  private async handleFixCommand(args: string[], message: ChatMessage): Promise<void> {
    if (args.length === 0 || !args[0]) {
      await this.sendMessage(message.channelId, "Usage: `@mosaic fix <issue-number>`");
      return;
    }

    const issueNumber = parseInt(args[0], 10);

    if (isNaN(issueNumber)) {
      await this.sendMessage(
        message.channelId,
        "Invalid issue number. Please provide a numeric issue number."
      );
      return;
    }

    // Create thread for job updates
    const threadId = await this.createThread({
      channelId: message.channelId,
      name: `Job #${String(issueNumber)}`,
      message: `Starting job for issue #${String(issueNumber)}...`,
    });

    // Dispatch job to stitcher
    const result = await this.stitcherService.dispatchJob({
      workspaceId: this.workspaceId,
      type: "code-task",
      priority: 10,
      metadata: {
        issueNumber,
        command: "fix",
        channelId: message.channelId,
        threadId: threadId,
        authorId: message.authorId,
        authorName: message.authorName,
      },
    });

    // Send confirmation to thread
    await this.sendThreadMessage({
      threadId,
      content: `Job created: ${result.jobId}\nStatus: ${result.status}\nQueue: ${result.queueName}`,
    });
  }

  /**
   * Handle status command - Get job status
   */
  private async handleStatusCommand(args: string[], message: ChatMessage): Promise<void> {
    if (args.length === 0 || !args[0]) {
      await this.sendMessage(message.channelId, "Usage: `@mosaic status <job-id>`");
      return;
    }

    const jobId = args[0];

    // TODO: Implement job status retrieval from stitcher
    await this.sendMessage(
      message.channelId,
      `Status command not yet implemented for job: ${jobId}`
    );
  }

  /**
   * Handle cancel command - Cancel a running job
   */
  private async handleCancelCommand(args: string[], message: ChatMessage): Promise<void> {
    if (args.length === 0 || !args[0]) {
      await this.sendMessage(message.channelId, "Usage: `@mosaic cancel <job-id>`");
      return;
    }

    const jobId = args[0];

    // TODO: Implement job cancellation in stitcher
    await this.sendMessage(
      message.channelId,
      `Cancel command not yet implemented for job: ${jobId}`
    );
  }

  /**
   * Handle verbose command - Stream full logs to thread
   */
  private async handleVerboseCommand(args: string[], message: ChatMessage): Promise<void> {
    if (args.length === 0 || !args[0]) {
      await this.sendMessage(message.channelId, "Usage: `@mosaic verbose <job-id>`");
      return;
    }

    const jobId = args[0];

    // TODO: Implement verbose logging
    await this.sendMessage(message.channelId, `Verbose mode not yet implemented for job: ${jobId}`);
  }

  /**
   * Handle quiet command - Reduce notifications
   */
  private async handleQuietCommand(_args: string[], message: ChatMessage): Promise<void> {
    // TODO: Implement quiet mode
    await this.sendMessage(
      message.channelId,
      "Quiet mode not yet implemented. Currently showing milestone updates only."
    );
  }

  /**
   * Handle help command - Show available commands
   */
  private async handleHelpCommand(_args: string[], message: ChatMessage): Promise<void> {
    const helpMessage = `
**Available commands:**

\`@mosaic fix <issue>\` - Start job for issue
\`@mosaic status <job>\` - Get job status
\`@mosaic cancel <job>\` - Cancel running job
\`@mosaic verbose <job>\` - Stream full logs to thread
\`@mosaic quiet\` - Reduce notifications
\`@mosaic help\` - Show this help message

**Noise Management:**
• Main channel: Low verbosity (milestones only)
• Job threads: Medium verbosity (step completions)
• DMs: Configurable per user
    `.trim();

    await this.sendMessage(message.channelId, helpMessage);
  }
}
