use std::path::PathBuf;
use std::sync::Arc;
use std::sync::Mutex;

use crate::library::func::calculate_sha256_hash;
use crate::query_row_map;
use crate::yt_meta;

#[derive(Debug, Default, Clone)]
pub struct Video {
    directory: Option<String>,
    path: Option<PathBuf>,
    title: Option<String>,
    hash: String,
    youtube_id: Option<String>,
    description: Option<String>,
    uploader_name: Option<String>,
    uploader_id: Option<String>,
    duration: Option<usize>,
    views: Option<usize>,
    categories: Option<Vec<String>>,
    tags: Option<Vec<String>>,
    upload_date: Option<String>,
    db: Option<Arc<Mutex<rusqlite::Connection>>>,
}

// Video properties
impl Video {
    fn get_video_info(&mut self) {
        log::info!("Fetching Video Metadata for {}", self.hash);
        let db = self.db.as_mut().unwrap().lock().unwrap();

        let res: Vec<(String, String)> = query_row_map!(
            db,
            "SELECT title, path FROM videos WHERE sha256 = ?1",
            &[&self.hash],
            |x| { Ok((x.get(0)?, x.get(1)?)) }
        );

        let res = res.first().unwrap();

        self.title = Some(res.0.clone());
        self.path = Some(std::path::Path::new(&res.1).to_path_buf());
    }

    fn get_youtube_meta_info(&mut self) {
        log::info!("Fetching YouTube Metadata for {}", self.hash);
        let db = self.db.as_mut().unwrap().lock().unwrap();

        let res: Vec<(String, String, String, String, String, usize)> = query_row_map!(
            db,
            "SELECT id, description, uploader_name, uploader_id, upload_date, views FROM youtube_meta WHERE id = (SELECT youtube_id FROM videos WHERE sha256 = ?1 LIMIT 1)",
            &[&self.hash],
            |x| { Ok(
               ( x.get(0)? , x.get(1)?, x.get(2)?, x.get(3)?, x.get(4)?, x.get(5)? )
            )
         }
        );

        if let Some(res) = res.first() {
            self.youtube_id = Some(res.0.clone());
            self.description = Some(res.1.clone());
            self.uploader_name = Some(res.2.clone());
            self.uploader_id = Some(res.3.clone());
            self.upload_date = Some(res.4.clone());
            self.views = Some(res.5);

            let res: Vec<String> = query_row_map!(
                db,
                "SELECT category FROM youtube_meta_categories WHERE youtube_id = ?1",
                &[self.youtube_id.as_ref().unwrap()],
                |x| { x.get(0) }
            );

            self.categories = Some(res);

            let res: Vec<String> = query_row_map!(
                db,
                "SELECT tag FROM youtube_meta_tags WHERE youtube_id = ?1",
                &[self.youtube_id.as_ref().unwrap()],
                |x| { x.get(0) }
            );

            self.tags = Some(res);
        }
    }

    pub fn title(&mut self) -> Option<&str> {
        if self.title.is_none() {
            self.get_video_info();
        }
        self.title.as_deref()
    }

    pub fn path(&mut self) -> Option<PathBuf> {
        if self.path.is_none() {
            self.get_video_info();
        }
        self.path.as_ref().map(std::clone::Clone::clone)
    }

    pub fn description(&mut self) -> Option<&str> {
        if self.description.is_none() {
            self.get_youtube_meta_info();
        }
        self.description.as_deref()
    }

    pub fn views(&mut self) -> Option<usize> {
        if self.views.is_none() {
            self.get_youtube_meta_info();
        }
        self.views
    }

    pub fn uploader_name(&mut self) -> Option<&str> {
        if self.uploader_name.is_none() {
            self.get_youtube_meta_info();
        }
        self.uploader_name.as_deref()
    }

    pub fn uploader_id(&mut self) -> Option<&str> {
        if self.uploader_id.is_none() {
            self.get_youtube_meta_info();
        }
        self.uploader_id.as_deref()
    }

    pub fn upload_date(&mut self) -> Option<&str> {
        if self.upload_date.is_none() {
            self.get_youtube_meta_info();
        }
        self.upload_date.as_deref()
    }

    pub fn categories(&mut self) -> Option<&Vec<String>> {
        if self.categories.is_none() {
            self.get_youtube_meta_info();
        }
        self.categories.as_ref()
    }

    pub fn tags(&mut self) -> Option<&Vec<String>> {
        if self.tags.is_none() {
            self.get_youtube_meta_info();
        }
        self.tags.as_ref()
    }

    pub fn youtube_id(&mut self) -> Option<&str> {
        if self.youtube_id.is_none() {
            self.get_youtube_meta_info();
        }
        self.youtube_id.as_deref()
    }

    pub fn hash(&self) -> &str {
        &self.hash
    }
}

// Video Init
impl Video {
    pub fn from_hash(hash: &str, db: Arc<Mutex<rusqlite::Connection>>) -> Self {
        Self {
            hash: hash.to_owned(),
            db: Some(db),
            ..Default::default()
        }
    }
    pub fn insert_path_to_db(db: &Arc<Mutex<rusqlite::Connection>>, v: &PathBuf) -> Self {
        log::info!("Add {v:?} to library");
        let id = calculate_sha256_hash(v.to_str().unwrap()).unwrap();
        let file_name = v.file_stem().unwrap().to_str().unwrap().to_owned();
        let dir = v
            .parent()
            .unwrap()
            .file_name()
            .unwrap()
            .to_str()
            .unwrap()
            .to_owned();

        let mut sdb = db.lock().unwrap();
        let tx = sdb.transaction().unwrap();

        if let Some(meta) = yt_meta::get_youtube_metadata(v) {
            tx.execute(
                "INSERT INTO videos (sha256, directory, path, title, youtube_id) VALUES (?1, ?2, ?3, ?4, ?5)", 
                [
                    &id,
                    &dir,
                    v.to_str().unwrap(),
                    &meta.title(),
                    &meta.youtube_id().unwrap()
            ]).unwrap();

            let _ = tx.execute(
                "INSERT INTO youtube_meta (id, title, description, uploader_name, uploader_id, duration, views, upload_date) VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8)",
            [
                &meta.youtube_id().unwrap(),
                &meta.title(),
                &meta.description().unwrap(),
                &meta.uploader_name().unwrap(),
                &meta.uploader_id().unwrap(),
                &meta.duration().unwrap().to_string(),
                &meta.views().unwrap().to_string(),
                &meta.upload_date().unwrap()
            ]);

            for cat in meta.categories().unwrap() {
                let _ = tx.execute(
                    "INSERT INTO youtube_meta_categories (youtube_id, category) VALUES (?1, ?2)",
                    [&meta.youtube_id().unwrap(), &cat],
                );
            }

            if let Some(tags) = meta.tags() {
                for tag in tags {
                    let _ = tx.execute(
                        "INSERT INTO youtube_meta_tags (youtube_id, tag) VALUES (?1, ?2)",
                        [&meta.youtube_id().unwrap(), &tag],
                    );
                }
            }

            tx.commit().unwrap();

            return Self {
                directory: Some(dir),
                path: Some(v.to_owned()),
                title: Some(meta.title()),
                hash: id,
                youtube_id: meta.youtube_id(),
                description: meta.description(),
                uploader_name: meta.uploader_name(),
                uploader_id: meta.uploader_id(),
                duration: meta.duration(),
                views: meta.views(),
                categories: meta.categories(),
                tags: meta.tags(),
                upload_date: meta.upload_date(),
                db: Some(db.clone()),
            };
        }

        tx.execute(
            "INSERT INTO videos (sha256, directory, path, title) VALUES (?1, ?2, ?3, ?4)",
            [&id, &dir, v.to_str().unwrap(), &file_name],
        )
        .unwrap();

        tx.commit().unwrap();

        Self {
            path: Some(v.to_owned()),
            title: Some(v.file_stem().unwrap().to_str().unwrap().to_owned()),
            hash: id,
            db: Some(db.clone()),
            ..Self::default()
        }
    }
}
