// 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::str::FromStr;

use anyhow::{bail, Context, Result};
use futures::FutureExt;
use log::warn;
use serde_json::{json, Value};
use tokio::io::{AsyncBufRead, AsyncBufReadExt};
use url::Url;
use yt_dlp::wrapper::info_json::InfoType;

use crate::{
    app::App,
    storage::subscriptions::{
        add_subscription, check_url, get_subscriptions, remove_all_subscriptions,
        remove_subscription, Subscription,
    },
};

pub async fn unsubscribe(app: &App, name: String) -> Result<()> {
    let present_subscriptions = get_subscriptions(&app).await?;

    if let Some(subscription) = present_subscriptions.0.get(&name) {
        remove_subscription(&app, subscription).await?;
    } else {
        bail!("Couldn't find subscription: '{}'", &name);
    }

    Ok(())
}

pub async fn import<W: AsyncBufRead + AsyncBufReadExt + Unpin>(
    app: &App,
    reader: W,
    force: bool,
) -> Result<()> {
    if force {
        remove_all_subscriptions(&app).await?;
    }

    let mut lines = reader.lines();
    while let Some(line) = lines.next_line().await? {
        let url =
            Url::from_str(&line).with_context(|| format!("Failed to parse '{}' as url", line))?;
        match subscribe(app, None, url)
            .await
            .with_context(|| format!("Failed to subscribe to: '{}'", line))
        {
            Ok(_) => (),
            Err(err) => eprintln!(
                "Error while subscribing to '{}': '{}'",
                line,
                err.source().expect("Should have a source").to_string()
            ),
        }
    }

    Ok(())
}

pub async fn subscribe(app: &App, name: Option<String>, url: Url) -> Result<()> {
    if !(url.as_str().ends_with("videos")
        || url.as_str().ends_with("streams")
        || url.as_str().ends_with("shorts")
        || url.as_str().ends_with("videos/")
        || url.as_str().ends_with("streams/")
        || url.as_str().ends_with("shorts/"))
        && url.as_str().contains("youtube.com")
    {
        warn!("Your youtbe url does not seem like it actually tracks a channels playlist (videos, streams, shorts). Adding subscriptions for each of them...");

        let url = Url::parse(&(url.as_str().to_owned() + "/"))
            .expect("This was an url, it should stay one");

        if let Some(name) = name {
            let out: Result<()> = async move {
                actual_subscribe(
                    &app,
                    Some(name.clone() + " {Videos}"),
                    url.join("videos/").expect("Works"),
                )
                .await
                .with_context(|| {
                    format!("Failed to subscribe to '{}'", name.clone() + " {Videos}")
                })?;

                actual_subscribe(
                    &app,
                    Some(name.clone() + " {Streams}"),
                    url.join("streams/").expect("Works"),
                )
                .await
                .with_context(|| {
                    format!("Failed to subscribe to '{}'", name.clone() + " {Streams}")
                })?;

                actual_subscribe(
                    &app,
                    Some(name.clone() + " {Shorts}"),
                    url.join("shorts/").expect("Works"),
                )
                .await
                .with_context(|| format!("Failed to subscribe to '{}'", name + " {Shorts}"))?;

                Ok(())
            }
            .boxed()
            .await;

            out?
        } else {
            actual_subscribe(&app, None, url.join("videos/").expect("Works"))
                .await
                .with_context(|| format!("Failed to subscribe to the '{}' variant", "{Videos}"))?;

            actual_subscribe(&app, None, url.join("streams/").expect("Works"))
                .await
                .with_context(|| format!("Failed to subscribe to the '{}' variant", "{Streams}"))?;

            actual_subscribe(&app, None, url.join("shorts/").expect("Works"))
                .await
                .with_context(|| format!("Failed to subscribe to the '{}' variant", "{Shorts}"))?;
        }
    } else {
        actual_subscribe(&app, name, url).await?;
    }

    Ok(())
}

async fn actual_subscribe(app: &App, name: Option<String>, url: Url) -> Result<()> {
    if !check_url(&url).await? {
        bail!("The url ('{}') does not represent a playlist!", &url)
    };

    let name = if let Some(name) = name {
        name
    } else {
        let yt_opts = match json!( {
            "playliststart": 1,
            "playlistend": 10,
            "noplaylist": false,
            "extract_flat": "in_playlist",
        }) {
            Value::Object(map) => map,
            _ => unreachable!("This is hardcoded"),
        };

        let info = yt_dlp::extract_info(&yt_opts, &url, false, false).await?;

        if info._type == Some(InfoType::Playlist) {
            info.title.expect("This should be some for a playlist")
        } else {
            bail!("The url ('{}') does not represent a playlist!", &url)
        }
    };

    let present_subscriptions = get_subscriptions(&app).await?;

    if let Some(subs) = present_subscriptions.0.get(&name) {
        bail!(
            "The subscription '{}' could not be added, \
                as another one with the same name ('{}') already exists. It links to the Url: '{}'",
            name,
            name,
            subs.url
        );
    }

    let sub = Subscription { name, url };

    add_subscription(&app, &sub).await?;

    Ok(())
}