// yt - A fully featured command line YouTube client
//
// Copyright (C) 2024 Benedikt Peetz <benedikt.peetz@b-peetz.de>
// SPDX-License-Identifier: GPL-3.0-or-later
//
// This file is part of Yt.
//
// You should have received a copy of the License along with this program.
// If not, see <https://www.gnu.org/licenses/gpl-3.0.txt>.

use std::{
    env::{self},
    fs,
    io::{BufRead, Write},
    io::{BufReader, BufWriter},
};

use crate::{
    app::App,
    cli::CliArgs,
    constants::HELP_STR,
    storage::video_database::{getters::get_videos, VideoStatus},
};

use anyhow::{bail, Context, Result};
use clap::Parser;
use cmds::handle_select_cmd;
use futures::future::join_all;
use selection_file::process_line;
use tempfile::Builder;
use tokio::process::Command;

pub mod cmds;
pub mod selection_file;

pub async fn select(app: &App, done: bool, use_last_selection: bool) -> Result<()> {
    let temp_file = Builder::new()
        .prefix("yt_video_select-")
        .suffix(".yts")
        .rand_bytes(6)
        .tempfile()
        .context("Failed to get tempfile")?;

    if use_last_selection {
        fs::copy(&app.config.paths.last_selection_path, &temp_file)?;
    } else {
        let matching_videos = if done {
            get_videos(app, VideoStatus::ALL, None).await?
        } else {
            get_videos(
                app,
                &[
                    VideoStatus::Pick,
                    //
                    VideoStatus::Watch,
                    VideoStatus::Cached,
                ],
                None,
            )
            .await?
        };

        // Warmup the cache for the display rendering of the videos.
        // Otherwise the futures would all try to warm it up at the same time.
        if let Some(vid) = matching_videos.get(0) {
            let _ = vid.to_select_file_display(app).await?;
        }

        let mut edit_file = BufWriter::new(&temp_file);

        join_all(
            matching_videos
                .iter()
                .map(|vid| async { vid.to_select_file_display(app).await })
                .collect::<Vec<_>>(),
        )
        .await
        .into_iter()
        .try_for_each(|line| -> Result<()> {
            let line = line?;
            edit_file
                .write_all(line.as_bytes())
                .expect("This write should not fail");

            Ok(())
        })?;

        edit_file.write_all(HELP_STR.as_bytes())?;
        edit_file.flush().context("Failed to flush edit file")?;
    };

    {
        let editor = env::var("EDITOR").unwrap_or("nvim".to_owned());

        let mut nvim = Command::new(editor);
        nvim.arg(temp_file.path());
        let status = nvim.status().await.context("Falied to run nvim")?;
        if !status.success() {
            bail!("nvim exited with error status: {}", status)
        }
    }

    let read_file = temp_file.reopen()?;
    fs::copy(temp_file.path(), &app.config.paths.last_selection_path)
        .context("Failed to persist selection file")?;

    let reader = BufReader::new(&read_file);

    let mut line_number = 0;
    for line in reader.lines() {
        let line = line.context("Failed to read a line")?;

        if let Some(line) = process_line(&line)? {
            line_number -= 1;

            // debug!(
            //     "Parsed command: `{}`",
            //     line.iter()
            //         .map(|val| format!("\"{}\"", val))
            //         .collect::<Vec<String>>()
            //         .join(" ")
            // );

            let arg_line = ["yt", "select"]
                .into_iter()
                .chain(line.iter().map(|val| val.as_str()));

            let args = CliArgs::parse_from(arg_line);

            let cmd = if let crate::cli::Command::Select { cmd } =
                args.command.expect("This will be some")
            {
                cmd
            } else {
                unreachable!("This is checked in the `filter_line` function")
            };

            handle_select_cmd(
                &app,
                cmd.expect("This value should always be some here"),
                Some(line_number),
            )
            .await?
        }
    }

    Ok(())
}

// // FIXME: There should be no reason why we need to re-run yt, just to get the help string. But I've
// // yet to find a way to do it with out the extra exec <2024-08-20>
// async fn get_help() -> Result<String> {
//     let binary_name = current_exe()?;
//     let cmd = Command::new(binary_name)
//         .args(&["select", "--help"])
//         .output()
//         .await?;
//
//     assert_eq!(cmd.status.code(), Some(0));
//
//     let output = String::from_utf8(cmd.stdout).expect("Our help output was not utf8?");
//
//     let out = output
//         .lines()
//         .map(|line| format!("# {}\n", line))
//         .collect::<String>();
//
//     debug!("Returning help: '{}'", &out);
//
//     Ok(out)
// }