import fs from "fs";
import { EventEmitter } from "events";
import {
  AudioPlayerStatus,
  NoSubscriberBehavior,
  VoiceConnectionStatus,
  createAudioPlayer,
  createAudioResource,
  entersState,
  joinVoiceChannel,
} from "@discordjs/voice";
import { Events } from "discord.js";
import { config } from "../config.js";
import {
  deleteTrackFile,
  handleTrackCompletion,
} from "../web/storage/storageManager.js";

const AUTO_DISCONNECT_DELAY = 5 * 60 * 1000;

class MusicQueue extends EventEmitter {
  constructor(client) {
    super();
    this.client = client;
    this.queue = [];
    this.currentTrack = null;
    this.connection = null;
    this.playbackStatus = AudioPlayerStatus.Idle;
    this.idleDisconnectTimeout = null;
    this.aloneDisconnectTimeout = null;
    this.suspendedForAlone = false;
    this.audioPlayer = createAudioPlayer({
      behaviors: {
        noSubscriber: NoSubscriberBehavior.Play,
      },
    });
    this.voiceStateListener = (oldState, newState) => {
      if (
        oldState.channelId === config.voiceChannelId ||
        newState.channelId === config.voiceChannelId
      ) {
        this.evaluateAloneStatus();
      }
    };
    this.client.on(Events.VoiceStateUpdate, this.voiceStateListener);

    this.audioPlayer.on(AudioPlayerStatus.Idle, () => {
      if (this.currentTrack) {
        handleTrackCompletion(this.currentTrack);
      }
      this.currentTrack = null;
      this.playNext();
    });

    this.audioPlayer.on("error", (error) => {
      console.error("Audio player encountered an error", error);
      this.currentTrack = null;
      this.playNext();
    });

    this.audioPlayer.on("stateChange", (oldState, newState) => {
      this.playbackStatus = newState.status;
      this.emit("queueUpdate", this.snapshot());
    });
  }

  snapshot() {
    return {
      current: this.currentTrack,
      upcoming: this.queue,
      status: this.playbackStatus,
    };
  }

  async ensureConnection() {
    if (
      this.connection &&
      this.connection.joinConfig.channelId === config.voiceChannelId
    ) {
      return this.connection;
    }

    let channel = this.client.channels.cache.get(config.voiceChannelId);

    if (!channel) {
      try {
        channel = await this.client.channels.fetch(config.voiceChannelId);
      } catch (error) {
        console.error("Unable to fetch voice channel", error);
        throw error;
      }
    }

    if (!channel) {
      throw new Error("Voice channel configured for music playback was not found");
    }

    const connection = joinVoiceChannel({
      channelId: channel.id,
      guildId: channel.guild.id,
      adapterCreator: channel.guild.voiceAdapterCreator,
      selfDeaf: false,
    });

    this.connection = connection;
    connection.subscribe(this.audioPlayer);
    await entersState(connection, VoiceConnectionStatus.Ready, 30_000);
    this.evaluateAloneStatus();
    return connection;
  }

  enqueue(track) {
    this.queue.push(track);
    this.cancelIdleDisconnect();
    this.emit("queueUpdate", this.snapshot());
    if (!this.currentTrack) {
      return this.playNext();
    }

    return track;
  }

  async playNext() {
    if (this.suspendedForAlone) {
      return;
    }

    if (this.queue.length === 0) {
      this.scheduleIdleDisconnect();
      this.emit("queueUpdate", this.snapshot());
      return;
    }
    this.cancelIdleDisconnect();

    const nextTrack = this.queue.shift();
    this.emit("queueUpdate", this.snapshot());

    try {
      await this.ensureConnection();
    } catch (error) {
      console.error("Unable to connect to the configured voice channel", error);
      deleteTrackFile(nextTrack);
      this.currentTrack = null;
      return;
    }

    try {
      const stream = fs.createReadStream(nextTrack.path);
      const resource = createAudioResource(stream, {
        inlineVolume: true,
      });

      this.currentTrack = nextTrack;
      this.audioPlayer.play(resource);
      this.emit("trackStart", nextTrack);
    } catch (error) {
      console.error("Unable to play track", error);
      deleteTrackFile(nextTrack);
      this.currentTrack = null;
      return this.playNext();
    }
  }

  async remove(trackId) {
    const index = this.queue.findIndex((track) => track.id === trackId);
    if (index === -1) {
      if (this.currentTrack && this.currentTrack.id === trackId) {
        this.skip();
        return this.currentTrack;
      }
      return null;
    }

    const [removed] = this.queue.splice(index, 1);
    deleteTrackFile(removed);
    this.emit("queueUpdate", this.snapshot());
    if (this.queue.length === 0 && !this.currentTrack) {
      this.scheduleIdleDisconnect();
    }
    return removed;
  }

  move(trackId, newPosition) {
    const index = this.queue.findIndex((track) => track.id === trackId);
    if (index === -1) {
      return null;
    }

    const [track] = this.queue.splice(index, 1);
    const boundedPosition = Math.max(0, Math.min(newPosition, this.queue.length));
    this.queue.splice(boundedPosition, 0, track);
    this.emit("queueUpdate", this.snapshot());
    return this.snapshot();
  }

  skip() {
    this.audioPlayer.stop(true);
  }

  stop() {
    this.queue.forEach((track) => deleteTrackFile(track));
    this.queue = [];
    this.audioPlayer.stop(true);
    this.emit("queueUpdate", this.snapshot());
  }

  pause() {
    this.audioPlayer.pause(true);
  }

  resume() {
    this.audioPlayer.unpause();
  }

  start() {
    this.suspendedForAlone = false;
    this.cancelIdleDisconnect();
    if (!this.currentTrack) {
      return this.playNext();
    }
    return this.currentTrack;
  }

  removeByLibraryId(libraryTrackId) {
    if (!libraryTrackId) {
      return [];
    }

    const removed = this.queue.filter(
      (track) => track.libraryTrackId === libraryTrackId
    );

    this.queue = this.queue.filter(
      (track) => track.libraryTrackId !== libraryTrackId
    );

    removed.forEach((track) => deleteTrackFile(track));

    if (
      this.currentTrack &&
      this.currentTrack.libraryTrackId === libraryTrackId
    ) {
      this.skip();
    }

    if (removed.length > 0) {
      this.emit("queueUpdate", this.snapshot());
    }

    return removed;
  }

  destroyConnection(reason, { suspend = false } = {}) {
    if (!this.connection && this.playbackStatus === AudioPlayerStatus.Idle) {
      return;
    }

    if (suspend) {
      this.suspendedForAlone = true;
    }

    this.cancelIdleDisconnect();
    this.cancelAloneDisconnect();

    if (this.connection) {
      try {
        this.connection.destroy();
      } catch (error) {
        console.error("Failed to destroy voice connection", error);
      }
      this.connection = null;
    }

    try {
      this.audioPlayer.stop(true);
    } catch (error) {
      console.error("Failed to stop audio player", error);
    }

    this.currentTrack = null;
    this.emit("queueUpdate", this.snapshot());
    console.log(
      `[${new Date().toLocaleString()}] MusicQueue disconnected (${reason})`
    );
  }

  scheduleIdleDisconnect() {
    if (
      this.idleDisconnectTimeout ||
      this.queue.length > 0 ||
      this.currentTrack ||
      !this.connection
    ) {
      return;
    }

    this.idleDisconnectTimeout = setTimeout(() => {
      this.idleDisconnectTimeout = null;
      if (!this.currentTrack && this.queue.length === 0 && this.connection) {
        this.destroyConnection("idle timeout");
      }
    }, AUTO_DISCONNECT_DELAY);
  }

  cancelIdleDisconnect() {
    if (this.idleDisconnectTimeout) {
      clearTimeout(this.idleDisconnectTimeout);
      this.idleDisconnectTimeout = null;
    }
  }

  scheduleAloneDisconnect() {
    if (this.aloneDisconnectTimeout || !this.connection) {
      return;
    }

    this.aloneDisconnectTimeout = setTimeout(() => {
      this.aloneDisconnectTimeout = null;
      if (this.connection && this.isBotAlone()) {
        this.destroyConnection("alone timeout", { suspend: true });
      }
    }, AUTO_DISCONNECT_DELAY);
  }

  cancelAloneDisconnect() {
    if (this.aloneDisconnectTimeout) {
      clearTimeout(this.aloneDisconnectTimeout);
      this.aloneDisconnectTimeout = null;
    }
  }

  evaluateAloneStatus() {
    if (!this.connection) {
      this.cancelAloneDisconnect();
      return;
    }

    if (this.isBotAlone()) {
      this.scheduleAloneDisconnect();
    } else {
      const wasSuspended = this.suspendedForAlone;
      this.suspendedForAlone = false;
      this.cancelAloneDisconnect();
      if (wasSuspended && !this.currentTrack && this.queue.length > 0) {
        this.playNext();
      }
    }
  }

  isBotAlone() {
    const channel = this.client.channels.cache.get(config.voiceChannelId);
    if (!channel) {
      return false;
    }

    const clientId = this.client.user?.id;
    if (!clientId) {
      return false;
    }

    if (!channel.members.has(clientId)) {
      return false;
    }

    const others = channel.members.filter((member) => member.id !== clientId);
    return others.size === 0;
  }
}

export { MusicQueue };
