// 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::path::PathBuf;

use chrono::DateTime;
use format_video::FormatVideo;
use owo_colors::OwoColorize;
use url::Url;

use crate::{
    app::App,
    select::selection_file::duration::Duration,
    storage::video_database::{getters::get_video_opts, Video},
};

use anyhow::{Context, Result};

pub mod format_video;

macro_rules! get {
    ($value:expr, $key:ident, $name:expr, $code:tt) => {
        if let Some(value) = &$value.$key {
            $code(value)
        } else {
            concat!("[No ", $name, "]").to_owned()
        }
    };
}

/// This is identical to a [`FormattedVideo`], but has colorized fields.
pub struct ColorizedFormattedVideo(FormattedVideo);

impl FormattedVideo {
    pub fn colorize(self) -> ColorizedFormattedVideo {
        let Self {
            cache_path,
            description,
            duration,
            extractor_hash,
            last_status_change,
            parent_subscription_name,
            priority,
            publish_date,
            status,
            status_change,
            thumbnail_url,
            title,
            url,
            video_options,
        } = self;

        ColorizedFormattedVideo(Self {
            cache_path: cache_path.blue().bold().to_string(),
            description,
            duration: duration.cyan().bold().to_string(),
            extractor_hash: extractor_hash.bright_purple().italic().to_string(),
            last_status_change: last_status_change.bright_cyan().to_string(),
            parent_subscription_name: parent_subscription_name.bright_magenta().to_string(),
            priority,
            publish_date: publish_date.bright_white().bold().to_string(),
            status: status.red().bold().to_string(),
            status_change,
            thumbnail_url,
            title: title.green().bold().to_string(),
            url: url.italic().to_string(),
            video_options: video_options.bright_green().to_string(),
        })
    }
}

/// This is a version of [`Video`] that has all the fields of the original [`Video`] structure
/// turned to [`String`]s to facilitate displaying it.
///
/// This structure provides a way to display a [`Video`] in a coherent way, as it enforces to
/// always use the same colors for one field.
#[derive(Debug)]
pub struct FormattedVideo {
    cache_path: String,
    description: String,
    duration: String,
    extractor_hash: String,
    last_status_change: String,
    parent_subscription_name: String,
    priority: String,
    publish_date: String,
    status: String,
    status_change: String,
    thumbnail_url: String,
    title: String,
    url: String,
    /// This string contains the video options (speed, subtitle_languages, etc.).
    /// It already starts with an extra whitespace, when these are not empty.
    video_options: String,
}

impl Video {
    pub async fn to_formatted_video_owned(self, app: &App) -> Result<FormattedVideo> {
        Self::to_formatted_video(&self, app).await
    }

    pub async fn to_formatted_video(&self, app: &App) -> Result<FormattedVideo> {
        fn date_from_stamp(stamp: i64) -> String {
            DateTime::from_timestamp(stamp, 0)
                .expect("The timestamps should always be valid")
                .format("%Y-%m-%d")
                .to_string()
        }

        let cache_path: String = get!(
            self,
            cache_path,
            "Cache Path",
            (|value: &PathBuf| value.to_string_lossy().to_string())
        );
        let description = get!(
            self,
            description,
            "Description",
            (|value: &str| value.to_owned())
        );
        let duration = Duration::from(self.duration);
        let extractor_hash = self
            .extractor_hash
            .into_short_hash(app)
            .await
            .with_context(|| {
                format!(
                    "Failed to format extractor hash, whilst formatting video: '{}'",
                    self.title
                )
            })?;
        let last_status_change = date_from_stamp(self.last_status_change);
        let parent_subscription_name = get!(
            self,
            parent_subscription_name,
            "author",
            (|sub: &str| sub.replace('"', "'"))
        );
        let priority = self.priority;
        let publish_date = get!(
            self,
            publish_date,
            "release date",
            (|date: &i64| date_from_stamp(*date))
        );
        // TODO: We might support `.trim()`ing that, as the extra whitespace could be bad in the
        // selection file. <2024-10-07>
        let status = self.status.as_command();
        let status_change = self.status_change;
        let thumbnail_url = get!(
            self,
            thumbnail_url,
            "thumbnail URL",
            (|url: &Url| url.to_string())
        );
        let title = self.title.replace(['"', '„', '”'], "'");
        let url = self.url.as_str().replace('"', "\\\"");

        let video_options = {
            let opts = get_video_opts(app, &self.extractor_hash)
                .await
                .with_context(|| {
                    format!("Failed to get video options for video: '{}'", self.title)
                })?
                .to_cli_flags(app);
            let opts_white = if !opts.is_empty() { " " } else { "" };
            format!("{}{}", opts_white, opts)
        };

        Ok(FormattedVideo {
            cache_path,
            description,
            duration: duration.to_string(),
            extractor_hash: extractor_hash.to_string(),
            last_status_change,
            parent_subscription_name,
            priority: priority.to_string(),
            publish_date,
            status: status.to_string(),
            status_change: status_change.to_string(),
            thumbnail_url,
            title,
            url,
            video_options,
        })
    }
}

impl<'a> FormatVideo for &'a FormattedVideo {
    type Output = &'a str;

    fn cache_path(&self) -> Self::Output {
        &self.cache_path
    }

    fn description(&self) -> Self::Output {
        &self.description
    }

    fn duration(&self) -> Self::Output {
        &self.duration
    }

    fn extractor_hash(&self) -> Self::Output {
        &self.extractor_hash
    }

    fn last_status_change(&self) -> Self::Output {
        &self.last_status_change
    }

    fn parent_subscription_name(&self) -> Self::Output {
        &self.parent_subscription_name
    }

    fn priority(&self) -> Self::Output {
        &self.priority
    }

    fn publish_date(&self) -> Self::Output {
        &self.publish_date
    }

    fn status(&self) -> Self::Output {
        &self.status
    }

    fn status_change(&self) -> Self::Output {
        &self.status_change
    }

    fn thumbnail_url(&self) -> Self::Output {
        &self.thumbnail_url
    }

    fn title(&self) -> Self::Output {
        &self.title
    }

    fn url(&self) -> Self::Output {
        &self.url
    }

    fn video_options(&self) -> Self::Output {
        &self.video_options
    }
}
impl<'a> FormatVideo for &'a ColorizedFormattedVideo {
    type Output = &'a str;

    fn cache_path(&self) -> Self::Output {
        &self.0.cache_path
    }

    fn description(&self) -> Self::Output {
        &self.0.description
    }

    fn duration(&self) -> Self::Output {
        &self.0.duration
    }

    fn extractor_hash(&self) -> Self::Output {
        &self.0.extractor_hash
    }

    fn last_status_change(&self) -> Self::Output {
        &self.0.last_status_change
    }

    fn parent_subscription_name(&self) -> Self::Output {
        &self.0.parent_subscription_name
    }

    fn priority(&self) -> Self::Output {
        &self.0.priority
    }

    fn publish_date(&self) -> Self::Output {
        &self.0.publish_date
    }

    fn status(&self) -> Self::Output {
        &self.0.status
    }

    fn status_change(&self) -> Self::Output {
        &self.0.status_change
    }

    fn thumbnail_url(&self) -> Self::Output {
        &self.0.thumbnail_url
    }

    fn title(&self) -> Self::Output {
        &self.0.title
    }

    fn url(&self) -> Self::Output {
        &self.0.url
    }

    fn video_options(&self) -> Self::Output {
        &self.0.video_options
    }
}