use crate::download;
use crate::structs::*;
use crate::utils;
use anyhow::Result;
use clap_complete::{generate, Shell};
use download::download_episodes;
use futures::prelude::*;
use regex::Regex;

use rss::Channel;
use std::collections::HashSet;
use std::fs::{self, File};
use std::io::{self, BufReader, BufWriter, Write};
use std::path::PathBuf;

pub fn list_episodes(search: &str) -> Result<()> {
    let re = Regex::new(&format!("(?i){}", &search))?;
    let path = utils::get_xml_dir()?;

    for entry in fs::read_dir(&path)? {
        let entry = entry?;
        if re.is_match(&entry.file_name().into_string().unwrap()) {
            let file = File::open(&entry.path())?;
            let channel = Channel::read_from(BufReader::new(file))?;
            let podcast = Podcast::from(channel);
            let episodes = podcast.episodes();
            let stdout = io::stdout();
            let mut handle = stdout.lock();
            episodes
                .iter()
                .filter(|ep| ep.title().is_some())
                .enumerate()
                .for_each(|(num, ep)| {
                    writeln!(
                        &mut handle,
                        "({}) {}",
                        episodes.len() - num,
                        ep.title().unwrap()
                    )
                    .ok();
                });
            return Ok(());
        }
    }
    Ok(())
}

pub async fn update_subscription(
    state: &State,
    index: usize,
    sub: &Subscription,
    config: &Config,
) -> Result<[usize; 2]> {
    println!("Updating {}", sub.title);
    let mut path: PathBuf = utils::get_podcast_dir()?;
    path.push(&sub.title);
    utils::create_dir_if_not_exist(&path)?;

    let mut titles = HashSet::new();
    for entry in fs::read_dir(&path)? {
        let unwrapped_entry = &entry?;
        titles.insert(utils::trim_extension(
            &unwrapped_entry.file_name().into_string().unwrap(),
        ));
    }

    let resp = reqwest::get(&sub.url).await?.bytes().await?;
    let podcast = Podcast::from(Channel::read_from(BufReader::new(&resp[..]))?);

    let mut podcast_rss_path = utils::get_xml_dir()?;
    let title = utils::append_extension(podcast.title(), "xml");
    podcast_rss_path.push(title);

    let file = File::create(&podcast_rss_path)?;
    (*podcast).write_to(BufWriter::new(file))?;

    if sub.num_episodes < podcast.episodes().len() {
        let episodes = podcast.episodes()[..podcast.episodes().len() - sub.num_episodes].to_vec();
        let to_download = match config.download_subscription_limit {
            Some(subscription_limit) => {
                let download_futures = episodes
                    .iter()
                    .rev()
                    .take(subscription_limit as usize)
                    .map(|ep| Download::new(state, &podcast, ep));

                stream::iter(download_futures)
                    .filter_map(|download| async move { download.await.ok() })
                    .filter_map(|d| async move { d })
                    .collect::<Vec<Download>>()
                    .await
            }
            None => {
                let download_futures = episodes.iter().map(|ep| Download::new(state, &podcast, ep));

                stream::iter(download_futures)
                    .filter_map(|download| async move { download.await.ok() })
                    .filter_map(|d| async move { d })
                    .collect::<Vec<Download>>()
                    .await
            }
        };
        download_episodes(to_download).await?;
    }
    Ok([index, podcast.episodes().len()])
}

pub fn list_subscriptions(state: &State) -> Result<()> {
    let stdout = io::stdout();
    let mut handle = stdout.lock();
    for subscription in &state.subscriptions {
        writeln!(&mut handle, "{}", subscription.title())?;
    }
    Ok(())
}

pub fn print_completion(state: &State, arg: &str) {
    let command_name = "podcast";
    let mut app = crate::parser::get_app(&state.version);
    match arg {
        "zsh" => {
            generate(Shell::Zsh, &mut app, command_name, &mut io::stdout());
        }
        "bash" => {
            generate(Shell::Bash, &mut app, command_name, &mut io::stdout());
        }
        "powershell" => {
            generate(Shell::PowerShell, &mut app, command_name, &mut io::stdout());
        }
        "fish" => {
            generate(Shell::Fish, &mut app, command_name, &mut io::stdout());
        }
        "elvish" => {
            generate(Shell::Elvish, &mut app, command_name, &mut io::stdout());
        }
        other => {
            println!("Completions are not available for {}", other);
        }
    }
}