mod settings;
mod video;

mod shared_string;
use iced_video_player::{Video, VideoPlayer};
use shared_string::SharedString;

use std::sync::Arc;

mod blur_hash;
use blur_hash::BlurHash;

mod preview;
// use preview::Preview;

use iced::{Alignment, Element, Length, Task, widget::*};
use std::collections::{BTreeMap, BTreeSet};

#[derive(Debug, Clone)]
pub struct Loading {}

#[derive(Default, Debug, Clone)]
pub struct ItemCache {
    pub items: BTreeMap<uuid::Uuid, Item>,
    pub tree: BTreeMap<Option<uuid::Uuid>, BTreeSet<uuid::Uuid>>,
}

impl ItemCache {
    pub fn insert(&mut self, parent: impl Into<Option<uuid::Uuid>>, item: Item) {
        let parent = parent.into();
        self.tree.entry(parent).or_default().insert(item.id);
        self.items.insert(item.id, item);
    }

    pub fn extend<I: IntoIterator<Item = Item>>(
        &mut self,
        parent: impl Into<Option<uuid::Uuid>>,
        items: I,
    ) {
        let parent = parent.into();
        items.into_iter().for_each(|item| {
            self.insert(parent, item);
        });
    }

    pub fn items_of(&self, parent: impl Into<Option<uuid::Uuid>>) -> Vec<&Item> {
        let parent = parent.into();
        self.tree.get(&None);
        self.tree
            .get(&parent)
            .map(|ids| {
                ids.iter()
                    .filter_map(|id| self.items.get(id))
                    .collect::<Vec<&Item>>()
            })
            .unwrap_or_default()
    }

    pub fn get(&self, id: &uuid::Uuid) -> Option<&Item> {
        self.items.get(id)
    }
}

#[derive(Clone, Debug)]
pub struct Thumbnail {
    pub id: SharedString,
    pub blur_hash: Option<SharedString>,
}

impl From<api::jellyfin::BaseItemDto> for Item {
    fn from(dto: api::jellyfin::BaseItemDto) -> Self {
        Item {
            id: dto.id,
            name: dto.name.map(Into::into),
            parent_id: dto.parent_id,
            thumbnail: dto
                .image_tags
                .and_then(|tags| tags.get("Primary").cloned())
                .map(|tag| Thumbnail {
                    id: tag.clone().into(),
                    blur_hash: dto
                        .image_blur_hashes
                        .primary
                        .and_then(|hashes| hashes.get(&tag).cloned())
                        .map(|s| s.clone().into()),
                }),
            _type: dto._type,
        }
    }
}

#[derive(Clone, Debug)]
pub struct Item {
    pub id: uuid::Uuid,
    pub parent_id: Option<uuid::Uuid>,
    pub name: Option<SharedString>,
    pub thumbnail: Option<Thumbnail>,
    pub _type: api::jellyfin::BaseItemKind,
}

#[derive(Debug, Clone, Default)]
pub enum Screen {
    #[default]
    Home,
    Settings,
    User,
    Video,
}

#[derive(Debug, Clone)]
pub struct Config {
    pub server_url: Option<String>,
    pub device_id: Option<String>,
    pub device_name: Option<String>,
    pub client_name: Option<String>,
    pub version: Option<String>,
}

impl Default for Config {
    fn default() -> Self {
        Config {
            server_url: Some("http://localhost:8096".to_string()),
            device_id: Some("jello-iced".to_string()),
            device_name: Some("Jello Iced".to_string()),
            client_name: Some("Jello".to_string()),
            version: Some("0.1.0".to_string()),
        }
    }
}

#[derive(Debug, Clone)]
struct State {
    loading: Option<Loading>,
    current: Option<uuid::Uuid>,
    cache: ItemCache,
    jellyfin_client: Option<api::JellyfinClient>,
    messages: Vec<String>,
    history: Vec<Option<uuid::Uuid>>,
    query: Option<String>,
    screen: Screen,
    settings: settings::SettingsState,
    is_authenticated: bool,
    video: Option<Arc<Video>>,
}

impl State {
    pub fn new() -> Self {
        State {
            loading: None,
            current: None,
            cache: ItemCache::default(),
            jellyfin_client: None,
            messages: Vec::new(),
            history: Vec::new(),
            query: None,
            screen: Screen::Home,
            settings: settings::SettingsState::default(),
            // username_input: String::new(),
            // password_input: String::new(),
            is_authenticated: false,
            video: None,
        }
    }
}

#[derive(Debug, Clone)]
pub enum Message {
    Settings(settings::SettingsMessage),
    Refresh,
    Search,
    SearchQueryChanged(String),
    OpenItem(Option<uuid::Uuid>),
    LoadedItem(Option<uuid::Uuid>, Vec<Item>),
    Error(String),
    SetToken(String),
    Back,
    Home,
    // Login {
    //     username: String,
    //     password: String,
    //     config: api::JellyfinConfig,
    // },
    // LoginSuccess(String),
    // LoadedClient(api::JellyfinClient, bool),
    // Logout,
    Video(video::VideoMessage),
}

fn update(state: &mut State, message: Message) -> Task<Message> {
    // if let Some(client) = state.jellyfin_client.clone() {
    match message {
        Message::Settings(msg) => settings::update(&mut state.settings, msg),
        Message::OpenItem(id) => {
            if let Some(client) = state.jellyfin_client.clone() {
                use api::jellyfin::BaseItemKind::*;
                if let Some(cached) = id.as_ref().and_then(|id| state.cache.get(id))
                    && matches!(cached._type, Video | Movie | Episode)
                {
                    let url = client
                        .stream_url(id.expect("ID exists"))
                        .expect("Failed to get stream URL");
                    Task::done(Message::Video(video::VideoMessage::Open(url)))
                } else {
                    Task::perform(
                        async move {
                            let items: Result<Vec<Item>, api::JellyfinApiError> = client
                                .items(id)
                                .await
                                .map(|items| items.into_iter().map(Item::from).collect());
                            (id, items)
                        },
                        |(msg, items)| match items {
                            Err(e) => Message::Error(format!("Failed to load item: {}", e)),
                            Ok(items) => Message::LoadedItem(msg, items),
                        },
                    )
                }
            } else {
                Task::none()
            }
        }
        Message::LoadedItem(id, items) => {
            state.cache.extend(id, items);
            state.history.push(state.current);
            state.current = id;
            Task::none()
        }
        Message::Refresh => {
            if let Some(client) = state.jellyfin_client.clone() {
                let current = state.current;
                Task::perform(
                    async move {
                        let items: Result<Vec<Item>, api::JellyfinApiError> = client
                            .items(current)
                            .await
                            .map(|items| items.into_iter().map(Item::from).collect());
                        (current, items)
                    },
                    |(msg, items)| match items {
                        Err(e) => Message::Error(format!("Failed to refresh items: {}", e)),
                        Ok(items) => Message::LoadedItem(msg, items),
                    },
                )
            } else {
                Task::none()
            }
        }
        Message::Error(err) => {
            tracing::error!("Error: {}", err);
            state.messages.push(err);
            Task::none()
        }
        Message::SetToken(token) => {
            tracing::info!("Authenticated with token: {}", token);
            state
                .jellyfin_client
                .as_mut()
                .map(|mut client| client.set_token(token));
            state.is_authenticated = true;
            Task::none()
        }
        Message::Back => {
            state.current = state.history.pop().unwrap_or(None);
            Task::none()
        }
        Message::Home => {
            state.current = None;
            Task::done(Message::Refresh)
        }
        Message::SearchQueryChanged(query) => {
            state.query = Some(query);
            // Handle search query change
            Task::none()
        }
        Message::Search => {
            // Handle search action
            // let client = state.jellyfin_client.clone();
            if let Some(client) = state.jellyfin_client.clone() {
                let query = state.query.clone().unwrap_or_default();
                Task::perform(async move { client.search(query).await }, |r| match r {
                    Err(e) => Message::Error(format!("Search failed: {}", e)),
                    Ok(items) => {
                        let items = items.into_iter().map(Item::from).collect();
                        Message::LoadedItem(None, items)
                    }
                })
            } else {
                Task::none()
            }
        }
        Message::Video(msg) => video::update(state, msg),
        _ => todo!(),
    }
}

fn view(state: &State) -> Element<'_, Message> {
    match state.screen {
        Screen::Settings => settings::settings(state),
        Screen::Home | _ => home(state),
    }
}

fn home(state: &State) -> Element<'_, Message> {
    column([header(state), body(state), footer(state)])
        .width(Length::Fill)
        .height(Length::Fill)
        .into()
}

fn body(state: &State) -> Element<'_, Message> {
    if let Some(ref video) = state.video {
        video::player(video)
    } else {
        scrollable(
            container(
                Grid::with_children(state.cache.items_of(state.current).into_iter().map(card))
                    .fluid(400)
                    .spacing(50),
            )
            .padding(50)
            .align_x(Alignment::Center)
            // .align_y(Alignment::Center)
            .height(Length::Fill)
            .width(Length::Fill),
        )
        .height(Length::Fill)
        .into()
    }
}

fn header(state: &State) -> Element<'_, Message> {
    row([
        container(
            Button::new(
                Text::new(
                    state
                        .jellyfin_client
                        .as_ref()
                        .map(|c| c.config.server_url.as_str())
                        .unwrap_or("No Server"),
                )
                .align_x(Alignment::Start),
            )
            .on_press(Message::Home),
        )
        .padding(10)
        .width(Length::Fill)
        .height(Length::Fill)
        .align_x(Alignment::Start)
        .align_y(Alignment::Center)
        .style(container::rounded_box)
        .into(),
        search(state),
        container(
            row([
                button("Refresh").on_press(Message::Refresh).into(),
                button("Settings")
                    .on_press(Message::Settings(settings::SettingsMessage::Open))
                    .into(),
                button("TestVideo")
                    .on_press(Message::Video(video::VideoMessage::Test))
                    .into(),
            ])
            .spacing(10),
        )
        .padding(10)
        .width(Length::Fill)
        .height(Length::Fill)
        .align_x(Alignment::End)
        .align_y(Alignment::Center)
        .style(container::rounded_box)
        .into(),
    ])
    .align_y(Alignment::Center)
    .width(Length::Fill)
    .height(50)
    .into()
}

fn search(state: &State) -> Element<'_, Message> {
    container(
        TextInput::new("Search...", state.query.as_deref().unwrap_or_default())
            .padding(10)
            .size(16)
            .width(Length::Fill)
            .on_input(Message::SearchQueryChanged)
            .on_submit(Message::Search),
    )
    .padding(10)
    .width(Length::Fill)
    .height(Length::Shrink)
    .style(container::rounded_box)
    .into()
}

fn footer(state: &State) -> Element<'_, Message> {
    container(
        column(
            state
                .messages
                .iter()
                .map(|msg| Text::new(msg).size(12).into())
                .collect::<Vec<Element<'_, Message>>>(),
        )
        .spacing(5),
    )
    .padding(10)
    .width(Length::Fill)
    .height(Length::Shrink)
    .style(container::rounded_box)
    .into()
}

fn card(item: &Item) -> Element<'_, Message> {
    let name = item
        .name
        .as_ref()
        .map(|s| s.as_ref())
        .unwrap_or("Unnamed Item");
    MouseArea::new(
        container(
            column([
                BlurHash::new(
                    item.thumbnail
                        .as_ref()
                        .and_then(|t| t.blur_hash.as_ref())
                        .map(|s| s.as_ref())
                        .unwrap_or(""),
                )
                .width(Length::Fill)
                .height(Length::FillPortion(5))
                .into(),
                Text::new(name)
                    .size(16)
                    .align_y(Alignment::Center)
                    .height(Length::FillPortion(1))
                    .into(),
            ])
            .align_x(Alignment::Center)
            .width(Length::Fill)
            .height(Length::Fill),
        )
        .style(container::rounded_box),
    )
    .on_press(Message::OpenItem(Some(item.id)))
    .into()
}

fn init() -> (State, Task<Message>) {
    // Create a default config for initial state

    // let default_config = api::JellyfinConfig {
    //     server_url: "http://localhost:8096".parse().expect("Valid URL"),
    //     device_id: "jello-iced".to_string(),
    //     device_name: "Jello Iced".to_string(),
    //     client_name: "Jello".to_string(),
    //     version: "0.1.0".to_string(),
    // };
    // let default_client = api::JellyfinClient::new_with_config(default_config);

    (
        State::new(),
        Task::perform(
            async move {
                let config_str = std::fs::read_to_string("config.toml")
                    .map_err(|e| api::JellyfinApiError::IoError(e))?;
                let config: api::JellyfinConfig = toml::from_str(&config_str).map_err(|e| {
                    api::JellyfinApiError::IoError(std::io::Error::new(
                        std::io::ErrorKind::InvalidData,
                        e,
                    ))
                })?;

                // Try to load cached token and authenticate
                match std::fs::read_to_string(".session") {
                    Ok(token) => {
                        let client = api::JellyfinClient::pre_authenticated(token.trim(), config)?;
                        Ok((client, true))
                    }
                    Err(_) => {
                        // No cached token, create unauthenticated client
                        let client = api::JellyfinClient::new_with_config(config);
                        Ok((client, false))
                    }
                }
            },
            |result: Result<_, api::JellyfinApiError>| match result {
                // Ok((client, is_authenticated)) => Message::LoadedClient(client, is_authenticated),
                Err(e) => Message::Error(format!("Initialization failed: {}", e)),
                _ => Message::Error("Login Unimplemented".to_string()),
            },
        )
        .chain(Task::done(Message::Refresh)),
    )
}

pub fn ui() -> iced::Result {
    iced::application(init, update, view).run()
}
