// 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::{collections::HashMap, fs};

use anyhow::{bail, Context, Result};
use app::App;
use cache::invalidate;
use clap::Parser;
use cli::{CacheCommand, CheckCommand, SelectCommand, SubscriptionCommand};
use select::cmds::handle_select_cmd;
use tokio::{
    fs::File,
    io::{stdin, BufReader},
};
use url::Url;
use yt_dlp::wrapper::info_json::InfoJson;

use crate::{cli::Command, storage::subscriptions::get_subscriptions};

pub mod app;
pub mod cli;

pub mod cache;
pub mod comments;
pub mod constants;
pub mod download;
pub mod select;
pub mod status;
pub mod storage;
pub mod subscribe;
pub mod update;
pub mod watch;

#[tokio::main]
async fn main() -> Result<()> {
    let args = cli::CliArgs::parse();
    stderrlog::new()
        .module(module_path!())
        .modules(&["yt_dlp".to_owned(), "libmpv2".to_owned()])
        .quiet(args.quiet)
        .show_module_names(false)
        .color(stderrlog::ColorChoice::Auto)
        .verbosity(args.verbosity as usize)
        .timestamp(stderrlog::Timestamp::Off)
        .init()
        .expect("Let's just hope that this does not panic");

    let app = App::new().await?;

    match args.command.unwrap_or(Command::default()) {
        Command::Download { force } => {
            if force {
                invalidate(&app, true).await?;
            }

            download::Downloader::new().consume(&app).await?;
        }
        Command::Select { cmd } => {
            let cmd = cmd.unwrap_or(SelectCommand::default());

            match cmd {
                SelectCommand::File { done } => select::select(&app, done).await?,
                _ => handle_select_cmd(&app, cmd, None).await?,
            }
        }
        Command::Update {
            max_backlog,
            subscriptions,
            concurrent_processes,
        } => {
            let all_subs = get_subscriptions(&app).await?;

            for sub in &subscriptions {
                if let None = all_subs.0.get(sub) {
                    bail!(
                        "Your specified subscription to update '{}' is not a subscription!",
                        sub
                    )
                }
            }

            update::update(&app, max_backlog, subscriptions, concurrent_processes).await?;
        }

        Command::Subscriptions { cmd } => match cmd {
            SubscriptionCommand::Add { name, url } => {
                subscribe::subscribe(&app, name, url)
                    .await
                    .context("Failed to add a subscription")?;
            }
            SubscriptionCommand::Remove { name } => {
                subscribe::unsubscribe(&app, name)
                    .await
                    .context("Failed to remove a subscription")?;
            }
            SubscriptionCommand::List { url } => {
                let all_subs = get_subscriptions(&app).await?;

                if url {
                    for val in all_subs.0.values() {
                        println!("{}", val.url);
                    }
                } else {
                    for (key, val) in all_subs.0 {
                        println!("{}: '{}'", key, val.url);
                    }
                }
            }
            SubscriptionCommand::Import { file, force } => {
                if let Some(file) = file {
                    let f = File::open(file).await?;

                    subscribe::import(&app, BufReader::new(f), force).await?
                } else {
                    subscribe::import(&app, BufReader::new(stdin()), force).await?
                };
            }
        },

        Command::Watch {} => watch::watch(&app).await?,

        Command::Status {} => status::show(&app).await?,

        Command::Database { command } => match command {
            CacheCommand::Invalidate { hard } => cache::invalidate(&app, hard).await?,
            CacheCommand::Maintain { all } => cache::maintain(&app, all).await?,
        },

        Command::Check { command } => match command {
            CheckCommand::InfoJson { path } => {
                let string = fs::read_to_string(&path)
                    .with_context(|| format!("Failed to read '{}' to string!", path.display()))?;

                let _: InfoJson =
                    serde_json::from_str(&string).context("Failed to deserialize value")?;
            }
            CheckCommand::UpdateInfoJson { path } => {
                let string = fs::read_to_string(&path)
                    .with_context(|| format!("Failed to read '{}' to string!", path.display()))?;

                let _: HashMap<Url, InfoJson> =
                    serde_json::from_str(&string).context("Failed to deserialize value")?;
            }
        },
        Command::Comments {} => {
            comments::comments(&app).await?;
        }
        Command::Description {} => {
            todo!()
            // description::description(&app).await?;
        }
    }

    Ok(())
}