const low = require('lowdb')
const FileSync = require('lowdb/adapters/FileSync')

var fs = require('fs');
const { uuid } = require('uuidv4');
var path = require('path');

var youtubedl = require('youtube-dl');
const config_api = require('./config');

const adapter = new FileSync('./appdata/db.json');
const db = low(adapter)

const debugMode = process.env.YTDL_MODE === 'debug';

async function subscribe(sub) {
    const result_obj = {
        success: false,
        error: ''
    };
    return new Promise(async resolve => {
        // sub should just have url and name. here we will get isPlaylist and path
        sub.isPlaylist = sub.url.includes('playlist');

        if (db.get('subscriptions').find({url: sub.url}).value()) {
            console.log('Sub already exists');
            result_obj.error = 'Subcription with URL ' + sub.url + ' already exists!';
            resolve(result_obj);
            return;
        }

        // add sub to db
        db.get('subscriptions').push(sub).write();

        let success = await getSubscriptionInfo(sub);
        result_obj.success = success;
        result_obj.sub = sub;
        getVideosForSub(sub);
        resolve(result_obj);
    });
    
}

async function getSubscriptionInfo(sub) {
    const basePath = config_api.getConfigItem('ytdl_subscriptions_base_path');
    return new Promise(resolve => {
        // get videos 
        let downloadConfig = ['--dump-json', '--playlist-end', '1']
        youtubedl.exec(sub.url, downloadConfig, {}, function(err, output) {
            if (debugMode) {
                console.log('Subscribe: got info for subscription ' + sub.id);
            }
            if (err) {
                console.log(err.stderr);
                resolve(false);
            } else if (output) {
                if (output.length === 0 || (output.length === 1 && output[0] === '')) {
                    if (debugMode) console.log('Could not get info for ' + sub.id);
                    resolve(false);
                }
                for (let i = 0; i < output.length; i++) {
                    let output_json = null;
                    try {
                        output_json = JSON.parse(output[i]);
                    } catch(e) {
                        output_json = null;
                    }
                    if (!output_json) {
                        continue;
                    }

                    if (!sub.name) {
                        sub.name = sub.isPlaylist ? output_json.playlist_title : output_json.uploader;
                        // if it's now valid, update
                        if (sub.name) {
                            db.get('subscriptions').find({id: sub.id}).assign({name: sub.name}).write();
                        }
                    }

                    if (!sub.archive) {
                        // must create the archive
                        const archive_dir = basePath + 'archives/' + sub.name;
                        const archive_path = path.join(archive_dir, 'archive.txt');

                        // creates archive directory and text file if it doesn't exist
                        if (!fs.existsSync(archive_dir)) {
                            fs.mkdirSync(archive_dir);
                            fs.closeSync(fs.openSync(archive_path, 'w'));
                        } else if (!fs.existsSync(archive_path)) {
                            fs.closeSync(fs.openSync(archive_path, 'w'));
                        }

                        // updates subscription
                        sub.archive = archive_dir;
                        db.get('subscriptions').find({id: sub.id}).assign({archive: archive_dir}).write();
                    }

                    // TODO: get even more info

                    resolve(true);
                }
                resolve(false);
            }
        });
    });
}

async function unsubscribe(sub, deleteMode) {
    return new Promise(async resolve => {
        const basePath = config_api.getConfigItem('ytdl_subscriptions_base_path');
        let result_obj = { success: false, error: '' };

        let id = sub.id;
        db.get('subscriptions').remove({id: id}).write();

        const appendedBasePath = getAppendedBasePath(sub, basePath);
        if (deleteMode && fs.existsSync(appendedBasePath)) {
            if (sub.archive && fs.existsSync(sub.archive)) {
                const archive_file_path = path.join(sub.archive, 'archive.txt');
                // deletes archive if it exists
                if (fs.existsSync(archive_file_path)) {
                    fs.unlinkSync(archive_file_path);
                }
                fs.rmdirSync(sub.archive);
            }
            deleteFolderRecursive(appendedBasePath);
        }
    });

}

async function deleteSubscriptionFile(sub, file, deleteForever) {
    const basePath = config_api.getConfigItem('ytdl_subscriptions_base_path');
    const useArchive = config_api.getConfigItem('ytdl_subscriptions_use_youtubedl_archive');
    const appendedBasePath = getAppendedBasePath(sub, basePath);
    const name = file;
    let retrievedID = null;
    return new Promise(resolve => {
        let filePath = appendedBasePath;
        var jsonPath = path.join(__dirname,filePath,name+'.info.json');
        var videoFilePath = path.join(__dirname,filePath,name+'.mp4');
        var imageFilePath = path.join(__dirname,filePath,name+'.jpg');

        jsonExists = fs.existsSync(jsonPath);
        videoFileExists = fs.existsSync(videoFilePath);
        imageFileExists = fs.existsSync(imageFilePath);

        if (jsonExists) {
            retrievedID = JSON.parse(fs.readFileSync(jsonPath, 'utf8'))['id'];
            fs.unlinkSync(jsonPath);
        }

        if (imageFileExists) {
            fs.unlinkSync(imageFilePath);
        }

        if (videoFileExists) {
            fs.unlink(videoFilePath, function(err) {
                if (fs.existsSync(jsonPath) || fs.existsSync(videoFilePath)) {
                    resolve(false);
                } else {
                    // check if the user wants the video to be redownloaded (deleteForever === false)
                    if (!deleteForever && useArchive && sub.archive && retrievedID) {
                        const archive_path = path.join(sub.archive, 'archive.txt')
                        // if archive exists, remove line with video ID
                        if (fs.existsSync(archive_path)) {
                            removeIDFromArchive(archive_path, retrievedID);
                        }
                    }
                    resolve(true);
                }
            });
        } else {
            // TODO: tell user that the file didn't exist
            resolve(true);
        }
        
    });
}

async function getVideosForSub(sub) {
    return new Promise(resolve => {
        if (!subExists(sub.id)) {
            resolve(false);
            return;
        }
        const basePath = config_api.getConfigItem('ytdl_subscriptions_base_path');
        const useArchive = config_api.getConfigItem('ytdl_subscriptions_use_youtubedl_archive');

        let appendedBasePath = null
        if (sub.name) {
            appendedBasePath = getAppendedBasePath(sub, basePath);
        } else {
            appendedBasePath = basePath + (sub.isPlaylist ? 'playlists/%(playlist_title)s' : 'channels/%(uploader)s');
        }

        let downloadConfig = ['-o', appendedBasePath + '/%(title)s.mp4', '-f', 'bestvideo[ext=mp4]+bestaudio[ext=m4a]/mp4', '-ciw', '--write-annotations', '--write-thumbnail', '--write-info-json', '--print-json'];

        if (sub.timerange) {
            downloadConfig.push('--dateafter', sub.timerange);
        }

        let archive_dir = null;
        let archive_path = null;

        if (useArchive) {
            if (sub.archive) {
                archive_dir = sub.archive;
                archive_path = path.join(archive_dir, 'archive.txt')
            }
            downloadConfig.push('--download-archive', archive_path);
        }

        // get videos 
        youtubedl.exec(sub.url, downloadConfig, {}, function(err, output) {
            if (debugMode) {
                console.log('Subscribe: got videos for subscription ' + sub.name);
            }
            if (err) {
                console.log(err.stderr);
                resolve(false);
            } else if (output) {
                if (output.length === 0 || (output.length === 1 && output[0] === '')) {
                    if (debugMode) console.log('No additional videos to download for ' + sub.name);
                    resolve(true);
                }
                for (let i = 0; i < output.length; i++) {
                    let output_json = null;
                    try {
                        output_json = JSON.parse(output[i]);
                    } catch(e) {
                        output_json = null;
                    }
                    if (!output_json) {
                        continue;
                    }

                    // TODO: Potentially store downloaded files in db?
        
                }
                resolve(true);
            }
        });
    });
}

function getAllSubscriptions() {
    const subscriptions = db.get('subscriptions').value();
    return subscriptions;
}

function getSubscription(subID) {
    return db.get('subscriptions').find({id: subID}).value();
}

function subExists(subID) {
    return !!db.get('subscriptions').find({id: subID}).value();
}

// helper functions

function getAppendedBasePath(sub, base_path) {
    return base_path + (sub.isPlaylist ? 'playlists/' : 'channels/') + sub.name;
}

// https://stackoverflow.com/a/32197381/8088021
const deleteFolderRecursive = function(folder_to_delete) {
    if (fs.existsSync(folder_to_delete)) {
      fs.readdirSync(folder_to_delete).forEach((file, index) => {
        const curPath = path.join(folder_to_delete, file);
        if (fs.lstatSync(curPath).isDirectory()) { // recurse
          deleteFolderRecursive(curPath);
        } else { // delete file
          fs.unlinkSync(curPath);
        }
      });
      fs.rmdirSync(folder_to_delete);
    }
  };

function removeIDFromArchive(archive_path, id) {
    let data = fs.readFileSync(archive_path, {encoding: 'utf-8'});
    if (!data) {
        console.log('Archive could not be found.');
        return;
    }

    let dataArray = data.split('\n'); // convert file data in an array
    const searchKeyword = id; // we are looking for a line, contains, key word id in the file
    let lastIndex = -1; // let say, we have not found the keyword

    for (let index=0; index<dataArray.length; index++) {
        if (dataArray[index].includes(searchKeyword)) { // check if a line contains the id keyword
            lastIndex = index; // found a line includes a id keyword
            break; 
        }
    }

    const line = dataArray.splice(lastIndex, 1); // remove the keyword id from the data Array

    // UPDATE FILE WITH NEW DATA
    const updatedData = dataArray.join('\n');
    fs.writeFileSync(archive_path, updatedData);
    if (line) return line;
    if (err) throw err;
}

module.exports = {
    getSubscription        : getSubscription,
    getAllSubscriptions    : getAllSubscriptions,
    subscribe              : subscribe,
    unsubscribe            : unsubscribe,
    deleteSubscriptionFile : deleteSubscriptionFile,
    getVideosForSub        : getVideosForSub,
    removeIDFromArchive    : removeIDFromArchive
}
