use crate::data_provider::words::{delete_group, delete_word, update_group, update_word};
use crate::dictionary::DictionaryMessage::*;
use crate::dictionary_test::DictionaryQuizState;
use crate::import::ImportState;
use crate::lang::{WordData, WordGroup};
use crate::navigation::Page::{Import, Word};
use crate::navigation::{NavigatedPage, Page};
use crate::styling::*;
use crate::word::WordState;
use crate::{AppState, RootMessage};
use chrono::{DateTime, TimeDelta, Utc};
use hashbrown::{HashMap, HashSet};
use iced::alignment::Vertical::Center;
use iced::widget::button::Style;
use iced::widget::button::{danger, text};
use iced::widget::space::horizontal;
use iced::widget::text_input::default;
use iced::widget::*;
use iced::{Border, Color, Shadow, Task};
use iced_core::Length::Fill;
use rand::random_range;
use std::fs;
use std::ops::Add;
use std::path::PathBuf;
use std::sync::Arc;
use parking_lot::Mutex;
use std::time::{Duration, Instant};

#[derive(Clone)]
pub struct DictionaryState {
    state: Arc<Mutex<AppState>>,
    include_map: Vec<bool>,
    tag_map: HashMap<String, bool>,
    reverse: bool,
    search: String,
    no_typing: bool,
    selected_group_index: usize,
    reverse_list: bool,
    auto_save_queue: HashMap<usize, DateTime<Utc>>,
}

#[derive(Debug, Clone)]
pub enum DictionaryMessage {
    Back,
    SetTags(usize, String),
    SetKey(usize, String),
    SetValue(usize, String),
    SubmitWord(usize),
    WordAction(usize),
    NewWord,
    Include(usize, bool),
    IncludeTag(String, bool),
    Test,
    ResetTags,
    SetReverse(bool),
    Search(String),
    SetTyping(bool),
    CreateGroup,
    EditGroup(String),
    SaveGroup,
    SelectGroup(usize),
    DeleteGroup,
    ChangeDirection,
    TrySave(usize),
    ToImport,
}

impl NavigatedPage<DictionaryMessage> for DictionaryState {
    fn navigate(&mut self, message: &DictionaryMessage) -> Option<Page> {
        if let Back = message {
            return Some(Page::PreviousPage);
        }
        if let Test = message
            && self.include_map.iter().any(|x| *x)
        {
            let mut words = vec![];
            let dict = &self.state.lock().dictionary;

            words = self
                .include_map
                .iter()
                .zip(0..self.include_map.len())
                .filter(|(flag, _)| **flag)
                .map(|(_, index)| dict[index].clone())
                .collect();

            return Some(Page::DictionaryQuiz(DictionaryQuizState::new(
                words,
                self.reverse,
                self.no_typing,
            )));
        }
        if let WordAction(index) = message {
            let word: WordData;
            {
                let state = self.state.lock();
                let dict = &state.dictionary;
                word = dict[*index].clone();
            }
            if word.id.is_valid() {
                return Some(Word(WordState::new(word, *index, self.state.clone())));
            }
        }
        if let ToImport = message {
            return Some(Import(ImportState::new(self.state.clone())));
        }
        None
    }

    fn navigated(&mut self) {
        let len = self.state.lock().dictionary.len();

        self.include_map = vec![false; len];
        self.tag_map = Default::default();
        self.update_tags();
    }

    fn update(&mut self, message: DictionaryMessage) -> Task<RootMessage> {
        match message {
            NewWord => {
                let mut state = self.state.lock();
                let mut word = WordData::new();
                word.group_id = state.word_groups[self.selected_group_index].id;

                let dict = &mut state.dictionary;
                dict.push(word);
                self.include_map.push(false);
            }

            SetKey(i, v) => {
                {
                    let dict = &mut self.state.lock().dictionary;
                    dict[i].key = v;
                }
                return self.launch_auto_save_offset(i);
            }
            SetValue(i, v) => {
                {
                    let dict = &mut self.state.lock().dictionary;
                    dict[i].value = v
                }
                return self.launch_auto_save_offset(i);
            }
            SetTags(i, mut v) => {
                {
                    let dict = &mut self.state.lock().dictionary;

                    let current_tags_value = dict[i].tags.clone();

                    if v.ends_with(", ") && v.len() < current_tags_value.len() {
                        v = v[..v.len() - 2].to_string()
                    }

                    while let Some(index) = v.find(",,") {
                        v.remove(index);
                    }

                    dict[i].tags = v;
                }

                self.update_tags();
                return self.launch_auto_save_offset(i);
            }

            WordAction(i) => {
                let state = &mut self.state.lock();
                let dict = &mut state.dictionary;
                let word = dict.remove(i);
                self.include_map.remove(i);
                self.auto_save_queue.remove(&i);
                delete_word(&word, &state.connection)
            }
            Include(i, b) => self.include_map[i] = b,
            IncludeTag(t, v) => {
                let index;
                {
                    let state = self.state.lock();
                    index = state.word_groups[self.selected_group_index].id;
                }
                self.tag_map.insert(t, v);
                self.update_words_include(index)
            }

            ResetTags => {
                self.tag_map.iter_mut().for_each(|(_, v)| *v = false);
                self.include_map.iter_mut().for_each(|x| *x = false)
            }
            SetReverse(v) => self.reverse = v,
            Search(s) => {
                self.search = s;
            }
            SetTyping(b) => self.no_typing = b,
            SubmitWord(i) => self.save_word(i),
            Back => {}
            Test => {}
            CreateGroup => {
                let state = &mut self.state.lock();

                state.word_groups.push(WordGroup {
                    id: 0.into(),
                    name: format!("Группа слов {}", random_range(100..1000)),
                });
            }
            EditGroup(new) => {
                let state = &mut self.state.lock();
                let group = state.word_groups.get_mut(self.selected_group_index);
                if let Some(group) = group {
                    group.name = new.clone();
                }
            }
            SaveGroup => {
                let state = &mut self.state.lock();
                let connection = &state.connection;
                let mut group = state.word_groups[self.selected_group_index].clone();

                update_group(&mut group, connection);
                state.word_groups[self.selected_group_index] = group;
            }
            SelectGroup(i) => {
                self.selected_group_index = i;
                let index;
                {
                    let state = self.state.lock();
                    index = state.word_groups[i].id;
                }
                self.update_words_include(index)
            }
            DeleteGroup => {
                if self.selected_group_index == 0 {
                    return Task::none();
                }
                {
                    let state = &mut self.state.lock();

                    if let Some(group) = state.word_groups.get(self.selected_group_index) {
                        let remove_group_id = group.id;
                        let connection = &state.connection;

                        delete_group(group, connection);
                        state.word_groups.remove(self.selected_group_index);
                        state
                            .dictionary
                            .retain(|word| word.group_id != remove_group_id);
                        self.selected_group_index = 0;
                    }
                }

                self.update_tags()
            }
            ChangeDirection => {
                self.reverse_list = !self.reverse_list;
            }
            TrySave(word_index) => {
                let now = Utc::now();
                if !self.auto_save_queue.contains_key(&word_index) {
                    return Task::none();
                }
                if self.auto_save_queue[&word_index] <= now {
                    self.auto_save_queue.remove(&word_index);
                    self.save_word(word_index);
                }
            }
            ToImport => {}
        }

        Task::none()
    }

    fn view(&self) -> iced::Element<'_, DictionaryMessage> {
        back_overlay(
            row![
                iced::widget::column![
                    self.groups_panel(),
                    row![horizontal().width(8), self.words_list(),],
                    row![
                        self.add_word_button(),
                        horizontal().width(Fill),
                        button("Импорт").style(text).on_press(ToImport),
                    ]
                ]
                .spacing(5),
                self.filters(),
            ]
            .spacing(DEFAULT_SPACING)
            .into(),
            Back,
        )
    }
}

impl DictionaryState {
    pub fn new(state: Arc<Mutex<AppState>>) -> Self {
        let len = state.lock().dictionary.len();

        let mut result = DictionaryState {
            include_map: vec![false; len],
            selected_group_index: 0,
            state,
            tag_map: HashMap::new(),
            reverse: false,
            search: "".to_string(),
            no_typing: true,
            reverse_list: true,
            auto_save_queue: HashMap::new(),
        };

        result.update_tags();

        result
    }

    fn save_word(&mut self, i: usize) {
        let state = &mut self.state.lock();
        let connection = &state.connection;
        let word = &mut state.dictionary[i].clone();

        update_word(word, connection);
        state.dictionary[i] = word.clone();
    }

    fn launch_auto_save_offset(&mut self, index: usize) -> Task<RootMessage> {
        let save_time = Utc::now().add(TimeDelta::milliseconds(900));
        self.auto_save_queue.insert(index, save_time);
        let message = RootMessage::Dictionary(TrySave(index));
        Task::perform(
            async { tokio::time::sleep(Duration::from_secs(1)).await },
            |_| message,
        )
    }

    fn words_list(&self) -> iced::Element<'_, DictionaryMessage> {
        let time = Instant::now();
        let mut col = Column::new().width(Fill);

        let state = self.state.lock();
        let group_id = state.word_groups[self.selected_group_index].id;

        let dict = &state.dictionary;

        let mut index = 0;

        for access_index in 0..dict.len() {
            let mut i = access_index;
            if self.reverse_list {
                i = dict.len() - access_index - 1;
            }
            let word = &dict[i];
            if word.group_id != group_id {
                continue;
            }

            if !self.search.is_empty()
                && !word.key.contains(&self.search)
                && !word.value.contains(&self.search)
                && !word.tags.contains(&self.search)
            {
                continue;
            }

            let word_line_data = WordLineState {
                is_included: self.include_map[i],
                key: word.key.clone(),
                value: word.value.clone(),
                tags: word.tags.clone(),
                id: word.id,
                index: i,
            };

            index += 1;

            let lazy_line = lazy(word_line_data, move |data| {
                let index = data.index;
                let mut line = Row::new().width(Fill).align_y(Center);
                line = line.push(
                    checkbox(data.is_included)
                        .label("")
                        .spacing(15)
                        .on_toggle(move |b| Include(index, b)),
                );

                line = line.push(
                    text_input("Слово", &data.key)
                        .size(ACCENT_FONT_SIZE)
                        .width(Fill)
                        .on_input(move |string| SetKey(index, string))
                        .on_submit(SubmitWord(index))
                        .style(|x, status| {
                            let mut default_style = default(x, status);
                            default_style.border.radius = 0.0.into();
                            default_style
                        }),
                );
                line = line.push(
                    text_input("Перевод", &data.value)
                        .size(ACCENT_FONT_SIZE)
                        .width(Fill)
                        .on_input(move |string| SetValue(index, string))
                        .on_submit(SubmitWord(index))
                        .style(|x, status| {
                            let mut default_style = default(x, status);
                            default_style.border.radius = 0.0.into();
                            default_style
                        }),
                );

                line = line.push(
                    text_input("Теги", &data.tags)
                        .size(ACCENT_FONT_SIZE)
                        .width(Fill)
                        .on_input(move |string| SetTags(index, string))
                        .on_submit(SubmitWord(index))
                        .style(|x, status| {
                            let mut default_style = default(x, status);
                            default_style.border.radius = 0.0.into();
                            default_style
                        }),
                );

                let line_button = || {
                    let action = WordAction(index);

                    if !data.id.is_valid() {
                        return button("-").on_press(action).style(|_x, _status| Style {
                            background: None,
                            text_color: Color::BLACK,
                            border: Border::default(),
                            shadow: Shadow::default(),
                            snap: false,
                        });
                    }

                    button("").on_press(WordAction(index)).width(15)
                };
                line = line.push(line_button()).push(space().width(10));
                line
            });

            col = col.push(lazy_line);
        }

        println!("Drawing {index} lines");
        println!("Words rendering time: {:?}", time.elapsed());
        scrollable(col).height(Fill).into()
    }

    fn filters(&self) -> iced::Element<'_, DictionaryMessage> {
        let dict = &self.state.lock().dictionary;

        iced::widget::column![
            text_input("Поиск", &self.search)
                .on_input(Search)
                .width(Fill),
            text!("Всего слов: {}", dict.len()),
            text!(
                "Выбрано слов: {}",
                self.include_map.iter().filter(|i| **i).count()
            ),
            self.tags_selector(),
            toggler(self.no_typing)
                .label("Без набора")
                .on_toggle(SetTyping),
            toggler(self.reverse)
                .label("Обратный тест")
                .on_toggle(SetReverse),
            button(text!("Тест").center().width(Fill))
                .style(cta_button)
                .on_press(Test)
                .width(Fill),
        ]
        .width(250)
        .spacing(DEFAULT_SPACING)
        .into()
    }

    fn tags_selector(&self) -> iced::Element<'_, DictionaryMessage> {
        let mut col = Column::new().width(Fill);
        col = col.push(
            button("Сбросить")
                .on_press(ResetTags)
                .style(|x: &Theme, _status| Style {
                    background: None,
                    text_color: x.palette().primary,
                    border: Border::default(),
                    shadow: Shadow::default(),
                    snap: false,
                }),
        );
        let mut sorted_tags = self.tag_map.iter().collect::<Vec<_>>();
        sorted_tags.sort();
        for tag in sorted_tags {
            col = col.push(
                checkbox(*tag.1)
                    .label(tag.0)
                    .on_toggle(|x1| IncludeTag(tag.0.clone(), x1)),
            )
        }

        container(scrollable(col)).height(Fill).into()
    }

    fn update_tags(&mut self) {
        let mut tags_list: HashSet<String> = HashSet::new();
        let dict = &self.state.lock().dictionary;

        dict.iter().for_each(|element| {
            split_with_coma(element.tags.as_str())
                .iter()
                .for_each(|tag| {
                    tags_list.insert(tag.clone());
                });
        });

        let current_tags = self
            .tag_map
            .keys()
            .map(|k| k.clone().to_string())
            .collect::<Vec<String>>();

        current_tags
            .iter()
            .filter(|tag| !tags_list.contains(*tag))
            .for_each(|tag| {
                self.tag_map.remove(tag);
            });

        tags_list.iter().for_each(|tag| {
            self.tag_map.insert(tag.clone(), false);
        });
    }

    fn update_words_include(&mut self, group_id: crate::lang::Id) {
        let include_tags = self
            .tag_map
            .iter()
            .filter(|(_, value)| **value)
            .map(|(t, _)| t.clone())
            .collect::<Vec<String>>();

        if include_tags.is_empty() {
            self.include_map.iter_mut().for_each(|x| *x = false);
            return;
        }

        let dict = &self.state.lock().dictionary;
        let time = Instant::now();

        self.include_map = dict
            .iter()
            .map(|word| (split_with_coma(word.tags.as_str()), word.group_id))
            .map(|(tags, word_group_id)| {
                !tags.is_empty()
                    && tags.iter().all(|t| include_tags.contains(t))
                    && word_group_id == group_id
            })
            .collect();

        println!("Time {}", time.elapsed().as_micros());
    }

    fn groups_panel(&self) -> iced::Element<'_, DictionaryMessage> {
        let mut row = Row::new();
        row = row.push(button("+").style(text).on_press(CreateGroup));

        let state = &self.state.lock();
        let groups = &state.word_groups;

        for (index, group) in groups.iter().enumerate() {
            row = row.push(
                button(text!("{}", group.name.clone()))
                    .style(text)
                    .on_press(SelectGroup(index)),
            );
        }

        let group = state.word_groups[self.selected_group_index].clone();

        iced::widget::column![
            scrollable(row).width(Fill).horizontal(),
            row![
                button("⇳").on_press(ChangeDirection).style(jl_button),
                text_input("Название группы слов", &group.name)
                    .on_input(EditGroup)
                    .width(250)
                    .on_submit(SaveGroup),
                horizontal(),
                self.group_delete_button(),
            ]
            .spacing(DEFAULT_SPACING / 2.0)
        ]
        .into()
    }

    fn group_delete_button(&self) -> iced::Element<'_, DictionaryMessage> {
        if self.selected_group_index != 0 {
            button("Удалить").style(danger).on_press(DeleteGroup).into()
        } else {
            space().into()
        }
    }

    fn add_word_button(&self) -> iced::Element<'_, DictionaryMessage> {
        let state = self.state.lock();
        let group_id = state.word_groups[self.selected_group_index].id;
        let button = button("Добавить слово").style(jl_button);

        if group_id.is_valid() {
            return button.on_press(NewWord).into();
        }

        button.into()
    }
}

pub fn split_with_coma(ts: &str) -> Vec<String> {
    ts.split(',')
        .map(|ts| ts.to_lowercase().trim().to_string())
        .filter(|t| !t.is_empty())
        .collect::<Vec<String>>()
}

pub fn app_data_dir() -> PathBuf {
    let mut dir = dirs::data_dir().unwrap();
    dir.push("jap_learn");
    if !dir.exists() {
        fs::create_dir_all(dir.clone()).unwrap();
    }

    dir
}

#[derive(Hash, PartialEq, Eq)]
struct WordLineState {
    is_included: bool,
    key: String,
    value: String,
    tags: String,
    id: crate::lang::Id,
    index: usize,
}
