import { format as formatDate } from "date-fns";
import fs from "fs";

import Mastodon from "mastodon";
import { v4 } from "uuid";
import axios from "axios";
import Pages from "./Pages";
import Export from "./Export";

import AlbumsModel from "../models/albums";
import JobsModel from "../models/jobs";
import UsersModel from "../models/users";
import ErrorEvent from "../libs/error";

import { getAlbumDetails } from "../helpers";

/**
 * Classe permettant la gestion des albums d'un utilisateur
 */
class Albums extends Pages {
    /**
     * Méthode permettant d'ajouter un album dans une collection
     * @param {Object} req
     * @return {Object}
     */
    static async postAddOne(req) {
        const { body, user } = req;
        const data = {
            ...body,
            discogsId: body.id,
            User: user._id,
        };
        data.released = data.released
            ? new Date(data.released.replace("-00", "-01"))
            : null;
        delete data.id;

        const album = new AlbumsModel(data);

        await album.save();

        const jobData = {
            model: "Albums",
            id: album._id,
        };
        const job = new JobsModel(jobData);

        job.save();

        try {
            const User = await UsersModel.findOne({ _id: user._id });

            const { mastodon: mastodonConfig } = User;

            const { publish, token, url, message } = mastodonConfig;

            if (publish && url && token) {
                const M = new Mastodon({
                    access_token: token,
                    api_url: url,
                });

                const video =
                    data.videos && data.videos.length > 0
                        ? data.videos[0].uri
                        : "";

                const status = `${(
                    message ||
                    "Je viens d'ajouter {artist} - {album} à ma collection !"
                )
                    .replaceAll("{artist}", data.artists[0].name)
                    .replaceAll("{format}", data.formats[0].name)
                    .replaceAll("{year}", data.year)
                    .replaceAll("{video}", video)
                    .replaceAll("{album}", data.title)}

Publié automatiquement via #musictopus`;

                const media_ids = [];

                if (data.images.length > 0) {
                    for (let i = 0; i < data.images.length; i += 1) {
                        if (media_ids.length === 4) {
                            break;
                        }

                        const filename = `${v4()}.jpg`;
                        const file = `/tmp/${filename}`;

                        // eslint-disable-next-line no-await-in-loop
                        const { data: buff } = await axios.get(
                            data.images[i].uri,
                            {
                                headers: {
                                    "User-Agent":
                                        "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:109.0) Gecko/20100101 Firefox/117.0",
                                },
                                responseType: "arraybuffer",
                            }
                        );

                        fs.writeFileSync(file, buff);

                        // eslint-disable-next-line no-await-in-loop
                        const { data: media } = await M.post("media", {
                            file: fs.createReadStream(file),
                        });

                        const { id } = media;

                        media_ids.push(id);

                        fs.unlinkSync(file);
                    }
                }

                await M.post("statuses", { status, media_ids });
            }
        } catch (err) {
            throw new ErrorEvent(
                500,
                "Mastodon",
                "Album ajouté à votre collection mais impossible de publier sur Mastodon"
            );
        }

        return album;
    }

    /**
     * Méthode permettant de récupérer les éléments distincts d'une collection
     * @param {String} field
     * @param {ObjectId} user
     * @return {Array}
     */
    static async getAllDistincts(field, user) {
        const distincts = await AlbumsModel.find(
            {
                User: user,
            },
            [],
            {
                sort: {
                    [field]: 1,
                },
            }
        ).distinct(field);

        return distincts;
    }

    /**
     * Méthode permettant de récupérer la liste des albums d'une collection
     * @return {Object}
     */
    async getAll() {
        const {
            page,
            limit,
            exportFormat = "json",
            sort = "artists_sort",
            order = "asc",
            artist,
            format,
            year,
            genre,
            style,
            userId: collectionUserId,
        } = this.req.query;

        let userId = this.req.user?._id;

        const where = {};

        if (artist) {
            where["artists.name"] = artist;
        }
        if (format) {
            where["formats.name"] = format;
        }
        if (year) {
            where.year = year;
        }
        if (genre) {
            where.genres = genre;
        }
        if (style) {
            where.styles = style;
        }

        if (!this.req.user && !collectionUserId) {
            throw new ErrorEvent(
                401,
                "Collection",
                "Cette collection n'est pas publique"
            );
        }

        if (collectionUserId) {
            const userIsSharingCollection = await UsersModel.findById(
                collectionUserId
            );

            if (
                !userIsSharingCollection ||
                !userIsSharingCollection.isPublicCollection
            ) {
                throw new ErrorEvent(
                    401,
                    "Collection",
                    "Cette collection n'est pas publique"
                );
            }

            userId = userIsSharingCollection._id;
        }

        const count = await AlbumsModel.count({
            User: userId,
            ...where,
        });

        let params = {
            sort: {
                [sort]: order.toLowerCase() === "asc" ? 1 : -1,
            },
        };

        if (page && limit) {
            const skip = (page - 1) * limit;

            params = {
                ...params,
                skip,
                limit,
            };
        }

        const rows = await AlbumsModel.find(
            {
                User: userId,
                ...where,
            },
            [],
            params
        );

        switch (exportFormat) {
            case "csv":
                return Export.convertToCsv(rows);
            case "xls":
                return Export.convertToXls(rows);
            case "xml":
                return Export.convertToXml(rows);
            case "musictopus":
                return Export.convertToMusicTopus(rows);
            case "json":
            default:
                return {
                    rows,
                    count,
                };
        }
    }

    /**
     * Méthode permettant de mettre à jour un album
     *
     * @return {Object}
     */
    async patchOne() {
        const { itemId: _id } = this.req.params;
        const { _id: User } = this.req.user;
        const query = {
            _id,
            User,
        };
        const album = await AlbumsModel.findOne(query);

        if (!album) {
            throw new ErrorEvent(
                404,
                "Mise à jour",
                "Impossible de trouver cet album"
            );
        }

        const values = await getAlbumDetails(album.discogsId);

        return AlbumsModel.findOneAndUpdate(query, values, { new: true });
    }

    /**
     * Méthode permettant de supprimer un élément d'une collection
     * @return {Boolean}
     */
    async deleteOne() {
        const res = await AlbumsModel.findOneAndDelete({
            User: this.req.user._id,
            _id: this.req.params.itemId,
        });

        if (res) {
            return true;
        }

        throw new ErrorEvent(
            404,
            "Suppression",
            "Impossible de trouver cet album"
        );
    }

    async shareOne() {
        const { message: status } = this.req.body;
        const { itemId: _id } = this.req.params;
        const { _id: User } = this.req.user;
        const query = {
            _id,
            User,
        };

        const album = await AlbumsModel.findOne(query);

        if (!album) {
            throw new ErrorEvent(
                404,
                "Mise à jour",
                "Impossible de trouver cet album"
            );
        }

        const { mastodon: mastodonConfig } = this.req.user;
        const { publish, token, url } = mastodonConfig;

        if (publish && url && token) {
            const M = new Mastodon({
                access_token: token,
                api_url: url,
            });

            const media_ids = [];

            if (album.images.length > 0) {
                for (let i = 0; i < album.images.length; i += 1) {
                    if (media_ids.length === 4) {
                        break;
                    }

                    const filename = `${v4()}.jpg`;
                    const file = `/tmp/${filename}`;

                    // eslint-disable-next-line no-await-in-loop
                    const { data: buff } = await axios.get(
                        album.images[i].uri,
                        {
                            headers: {
                                "User-Agent":
                                    "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:109.0) Gecko/20100101 Firefox/117.0",
                            },
                            responseType: "arraybuffer",
                        }
                    );

                    fs.writeFileSync(file, buff);

                    // eslint-disable-next-line no-await-in-loop
                    const { data: media } = await M.post("media", {
                        file: fs.createReadStream(file),
                    });

                    const { id } = media;

                    media_ids.push(id);

                    fs.unlinkSync(file);
                }
            }

            await M.post("statuses", { status, media_ids });
        } else {
            throw new ErrorEvent(
                406,
                `Vous n'avez pas configuré vos options de partage sur votre compte`
            );
        }

        return true;
    }

    /**
     * Méthode permettant de créer la page "ma-collection"
     */
    async loadMyCollection() {
        const artists = await Albums.getAllDistincts(
            "artists.name",
            this.req.user._id
        );
        const formats = await Albums.getAllDistincts(
            "formats.name",
            this.req.user._id
        );
        const years = await Albums.getAllDistincts("year", this.req.user._id);
        const genres = await Albums.getAllDistincts(
            "genres",
            this.req.user._id
        );
        const styles = await Albums.getAllDistincts(
            "styles",
            this.req.user._id
        );

        this.setPageContent("artists", artists);
        this.setPageContent("formats", formats);
        this.setPageContent("years", years);
        this.setPageContent("genres", genres);
        this.setPageContent("styles", styles);
        this.setPageTitle("Ma collection");
    }

    /**
     * Méthode permettant d'afficher le détails d'un album
     */
    async loadItem() {
        const { itemId: _id } = this.req.params;
        const { _id: User } = this.req.user;
        const album = await AlbumsModel.findOne({
            _id,
            User,
        });

        const item = {
            ...album.toJSON(),
            released: album.released
                ? formatDate(album.released, "MM/dd/yyyy")
                : null,
        };

        this.setPageContent("item", item);
        this.setPageTitle(
            `Détails de l'album ${item.title} de ${item.artists_sort}`
        );
    }

    /**
     * Méthode permettant de créer la page "collection/:userId"
     */
    async loadPublicCollection() {
        const { userId } = this.req.params;

        const user = await UsersModel.findById(userId);

        if (!user || !user.isPublicCollection) {
            throw new ErrorEvent(
                401,
                "Collection non partagée",
                "Cet utilisateur ne souhaite pas partager sa collection"
            );
        }

        const artists = await Albums.getAllDistincts("artists.name", userId);
        const formats = await Albums.getAllDistincts("formats.name", userId);
        const years = await Albums.getAllDistincts("year", userId);
        const genres = await Albums.getAllDistincts("genres", userId);
        const styles = await Albums.getAllDistincts("styles", userId);

        this.setPageContent("username", user.username);
        this.setPageTitle(`Collection publique de ${user.username}`);
        this.setPageContent("artists", artists);
        this.setPageContent("formats", formats);
        this.setPageContent("years", years);
        this.setPageContent("genres", genres);
        this.setPageContent("styles", styles);
    }
}

export default Albums;