use crate::data_provider::history::{get_history_of_set, history_dir};
use crate::data_provider::sqlite::{create_db, default_connection};
use crate::data_provider::web_api::{
    get_local_version, get_web_version, load_data, set_local_version,
};
use crate::dictionary::{DictionaryMessage, DictionaryState, app_data_dir};
use crate::dictionary_test::{DictionaryQuizMessage, DictionaryQuizState};
use crate::history::{HistoryMessage, HistoryState};
use crate::import::{ImportMessage, ImportState};
use crate::lang::Id;
use crate::message_navigation;
use crate::navigation::Page::*;
use crate::navigation::RootMessage::{DataLoaded, Keyboard, UpdateData};
use crate::quiz::{QuizMessage, QuizState};
use crate::randomizer::{RandomizerMessage, RandomizerState};
use crate::repetition::{RepetitionMessage, RepetitionState};
use crate::repetition_settings::{RepetitionSettingsMessage, RepetitionSettingsState};
use crate::repetitions::{RepetitionsMessage, RepetitionsState};
use crate::selector::{SelectorMessage, SelectorState};
use crate::state_navigate_handle;
use crate::state_update;
use crate::sync::{SyncMessage, SyncState};
use crate::view_navigation;
use crate::word::{WordMessage, WordState};
use crate::writing::{WritingMessage, WritingState};
use crate::{AppState, fill_state};
use chrono::NaiveDate;
use hashbrown::HashMap;
use iced::keyboard::Event;
use iced::{Element, Task};
use reqwest::Error;
use rusqlite::Connection;
use std::sync::Arc;
use parking_lot::Mutex;
use std::time::Instant;

impl Default for ScreenState {
    fn default() -> Self {
        let mut state = AppState::new();
        fill_state(&mut state);
        let state = Arc::new(Mutex::new(state));
        ScreenState {
            stack: vec![Selector(SelectorState::new(state.clone()))],
            app_state: state.clone(),
        }
    }
}

#[derive(Clone)]
pub enum RootMessage {
    Selector(SelectorMessage),
    Quiz(QuizMessage),
    Writing(WritingMessage),
    Dictionary(DictionaryMessage),
    DictionaryQuiz(DictionaryQuizMessage),
    Randomizer(RandomizerMessage),
    Repetitions(RepetitionsMessage),
    Repetition(RepetitionMessage),
    Word(WordMessage),
    Sync(SyncMessage),
    History(HistoryMessage),
    RepetitionSettings(RepetitionSettingsMessage),
    Import(ImportMessage),
    Keyboard(Event),
    DataLoaded(HashMap<Id, Vec<(NaiveDate, u32)>>),
    None,
    UpdateData,
}

pub enum Page {
    Selector(SelectorState),
    Quiz(QuizState),
    Writing(WritingState),
    Dictionary(DictionaryState),
    DictionaryQuiz(DictionaryQuizState),
    Randomizer(RandomizerState),
    Repetitions(RepetitionsState),
    Repetition(RepetitionState),
    Word(WordState),
    Sync(SyncState),
    History(HistoryState),
    RepetitionSettings(RepetitionSettingsState),
    Import(ImportState),
    PreviousPage,
}

pub struct ScreenState {
    stack: Vec<Page>,
    app_state: Arc<Mutex<AppState>>,
}

impl ScreenState {
    pub fn boot() -> (ScreenState, Task<RootMessage>) {
        println!("Booting");
        create_db();
        println!("Db created");
        let state = ScreenState::default();
        let reading_additional_task = Task::perform(Self::load_additional_data(), |data| data);

        let final_task;
        let state_guard = state.app_state.lock();
        if state_guard.sync_data.auto_web_fetch {
            let key = state_guard.sync_data.key.clone().unwrap();
            final_task = Task::batch([
                reading_additional_task,
                Task::perform(Self::load_web_backup(key), |result| {
                    if let Ok(update) = result
                        && update
                    {
                        UpdateData
                    } else {
                        RootMessage::None
                    }
                }),
            ]);
        } else {
            final_task = reading_additional_task;
        }

        drop(state_guard);
        println!("Boot finished");
        (state, final_task)
    }

    async fn load_additional_data() -> RootMessage {
        let directory = history_dir();
        let mut map = HashMap::new();
        for file in directory.read_dir().unwrap().flatten() {
            let mut vec = vec![];
            let history_file_name = match file.file_name().into_string() {
                Err(_) => continue,
                Ok(v) => v,
            };

            let id = history_file_name[4..history_file_name.len() - 12]
                .parse::<Id>()
                .unwrap();

            let history = get_history_of_set(id);
            let by_date = history.chunk_by(|x, x1| {
                x.timestamp.naive_local().date() == x1.timestamp.naive_local().date()
            });
            for group in by_date {
                vec.push((group[0].timestamp.naive_local().date(), group.len() as u32));
            }
            map.insert(id, vec);
        }
        DataLoaded(map)
    }

    async fn load_web_backup(string: String) -> Result<bool, Error> {
        let local = get_local_version().await;
        let web = get_web_version(string.as_str()).await?;
        println!("{} {}", local, web);
        if web > local {
            println!("Web version is newer than local version");
            load_data(string, true).await;
            set_local_version(web).await;
        } else {
            return Ok(false);
        }
        Ok(true)
    }

    pub fn update(&mut self, message: RootMessage) -> Task<RootMessage> {
        println!("Updating");
        let time = Instant::now();
        if let Keyboard(e) = message {
            return self.handle_keyboard(e);
        }

        if let DataLoaded(data) = message {
            self.app_state.lock().activity = data;
            return Task::none();
        }

        if let UpdateData = message {
            println!("Loading data");
            let mut state = self.app_state.lock();
            if cfg!(windows) {
                state.connection = Connection::open_in_memory().unwrap();
            }
            let path = app_data_dir();
            let db_file = path.join("data.db");
            let temp_db_file = path.join("data.db.tmp");
            std::fs::rename(temp_db_file, &db_file).unwrap();

            state.connection = default_connection();
            fill_state(&mut state);
            return Task::none();
        }
        let task: (Task<RootMessage>, bool) = self.navigate_or_update(message);

        if task.1 {
            state_navigate_handle!(
                self.stack,
                Selector,
                Quiz,
                Writing,
                Dictionary,
                DictionaryQuiz,
                Randomizer,
                Repetitions,
                Repetition,
                Word,
                Sync,
                History,
                RepetitionSettings,
                Import
            );
        }
        println!("Update took {:?}", time.elapsed());

        task.0
    }

    fn navigate_or_update(&mut self, message: RootMessage) -> (Task<RootMessage>, bool) {
        state_update!(
            message,
            self.stack,
            Selector,
            Quiz,
            Writing,
            Dictionary,
            DictionaryQuiz,
            Randomizer,
            Repetitions,
            Repetition,
            Word,
            Sync,
            History,
            RepetitionSettings,
            Import
        )
    }

    pub fn view(&self) -> Element<'_, RootMessage> {
        println!("View update");
        let time = Instant::now();
        let view = view_navigation!(
            self.stack,
            Quiz,
            Selector,
            Writing,
            Dictionary,
            DictionaryQuiz,
            Randomizer,
            Repetitions,
            Repetition,
            Word,
            Sync,
            History,
            RepetitionSettings,
            Import
        );

        println!("View took {:?}", time.elapsed());
        view
    }

    fn handle_keyboard(&mut self, message: Event) -> Task<RootMessage> {
        let page = self.stack.last_mut().unwrap();
        match page {
            Repetition(page) => page.press(&message),
            _ => Task::none(),
        }
    }
}

pub struct AppSettings {
    pub(crate) key: Option<String>,
    pub(crate) auto_web_fetch: bool,
}

#[macro_export]
macro_rules! view_navigation {
    ($stack:expr, $($e:ident), *) => {
        match &$stack.last().unwrap() {
            $(
            $e(s) => s.view().map(RootMessage::$e),
            )*
            _ => iced::widget::space().into(),
        }
    }
}

#[macro_export]
macro_rules! state_update {
    ($message:expr, $stack:expr, $($e:ident), *) => {
        match $message {
            $(
            RootMessage::$e(msg) => {
                if let $e(s) = $stack.last_mut().unwrap() {
                    message_navigation!(msg, $stack, s)
                }
                else {
                    return (Task::<RootMessage>::none(), false);
                }
            }
            )*
            _ => return (Task::<RootMessage>::none(), false),
        }
    }
}

#[macro_export]
macro_rules! message_navigation {
    ($msg:expr, $stack:expr, $state:expr) => {
        if let Some(new_page) = $state.navigate(&$msg) {
            if let Page::PreviousPage = new_page {
                if $stack.len() > 1 {
                    $stack.pop();
                }

                return (Task::<RootMessage>::none(), true);
            } else {
                $stack.push(new_page);
            }

            return (Task::<RootMessage>::none(), false);
        } else {
            return ($state.update($msg), false);
        }
    };
}

#[macro_export]
macro_rules! state_navigate_handle {
    ($stack:expr, $($e:ident), *) => {
        match $stack.last_mut().unwrap() {
            $(
            Page::$e(page) => {
                page.navigated();
            }
            )*
            _ => {},
        }
    }
}

pub trait NavigatedPage<T> {
    fn navigate(&mut self, message: &T) -> Option<Page>;
    fn navigated(&mut self);
    fn update(&mut self, message: T) -> Task<RootMessage>;
    fn view(&self) -> Element<'_, T>;
}

pub trait KeyPressedPage {
    fn press(&mut self, message: &Event) -> Task<RootMessage>;
}
