import { ISerializedTorrent } from "@autoplex-api/torrent";
import { MovieTicket, MovieTorrent } from "@autoplex/database";
import { link, mkdir } from "fs/promises";
import { dirname, extname } from "path";
import { safeTitleFileName } from "../../utils";
import Supervisor from "../Supervisor";
import { InternalService, Microservice } from "@autoplex/microservice";

/**
 * Common video file extensions
 */
const VIDEO_FILE_EXTENSIONS = new Set(
	".mp4,.mkv,.avi,.wmv,.mov,.m4v".split(",")
);

/**
 * A wrapper for a pending movie torrent
 */
interface IPendingMovieTorrent {
	details: ISerializedTorrent,
	diskName : string,
	ticket : MovieTicket
}

export default class PostProcessor extends InternalService
{
	/**
	 * The queue of movies to process
	 */
	protected pendingMovies: IPendingMovieTorrent[] = [];

	/**
	 * Indicate if movies are currently being processed
	 */
	protected isProcessingMovies: boolean = false;

	/**
	 * Create a new instance of the post processor
	 */
	public constructor(app: Microservice) {
		super(app);
		this.pendingMovies = [];
		this.isProcessingMovies = false;
	}

	/**
	 * The service name
	 */
	public readonly NAME = "Post Processor";

	// Methods -------------------------------------------------------------------------------------

	/**
	 * Enqueue a completed movie for post-processing
	 */
	public enqueueMovie(torrent: MovieTorrent, details: ISerializedTorrent) {
		this.pendingMovies.push({ticket: torrent.movieTicket, diskName: torrent.diskName, details});
		this.processMovies();
	}

	/**
	 * Process the enqueued movies
	 */
	public async processMovies() {
		if (this.isProcessingMovies) {
			return;
		}
		this.isProcessingMovies = true;
		while (this.pendingMovies.length > 0) {
			let movie = this.pendingMovies.splice(0, 1)[0];
			this.processMovie(movie);
		}
		this.isProcessingMovies = false;
	}

	/**
	 * Process an enqueue movie
	 */
	protected async processMovie(pendingMovie: IPendingMovieTorrent) {
		let movieFile = this.findMovieFile(pendingMovie.details);
		if (!movieFile) {
			this.log("Unable to find a completed movie file in the torrent:", pendingMovie.details.name);
			return;
		}

		// Generate the movie file's name
		let movieName = safeTitleFileName(pendingMovie.ticket.title);
		if (pendingMovie.ticket.year !== null) {
			movieName += ` (${pendingMovie.ticket.year})`;
		}

		let basePath = `/mnt/movies/${pendingMovie.diskName}`;
		let from = `${basePath}/Downloads/${movieFile.path}`;
		let to = `${basePath}/Media/${movieName}/${movieName}${extname(movieFile.path)}`;
		await this.hardlinkFile(from, to);

		// Notify the supervisor that the movie has been processed
		this.app.service<Supervisor>("Supervisor").onMovieTorrentProcessed(pendingMovie.ticket);
	}

	/**
	 * Locate the movie file in a torrent
	 */
	protected findMovieFile(details: ISerializedTorrent) {
		// Find video files in the torrent
		let movieFiles = details.files.filter(
			file => file.selected && file.downloaded == file.size
					&& VIDEO_FILE_EXTENSIONS.has(extname(file.path).toLowerCase())
		);
		// Find the video file with the largest size
		let maxIndex = 0;
		for (let i = 0; i < movieFiles.length; i++) {
			if (movieFiles[i].size > movieFiles[maxIndex].size) {
				maxIndex = i;
			}
		}
		// Return the result
		if (movieFiles[maxIndex] === undefined) {
			return null;
		}
		return movieFiles[maxIndex];
	}

	/**
	 * Hardlink a file to another directory
	 */
	protected async hardlinkFile(from: string, to: string) {
		this.log("Creating hardlink:", from, "->", to);
		await mkdir(dirname(to), { recursive: true });
		await link(from, to);
	}
}
