use crate::AppState;
use crate::data_provider::import::{ImportData, ImportGroup, get_words_of_group, load_groups};
use crate::data_provider::words::{add_group, add_words};
use crate::dictionary::app_data_dir;
use crate::import::ImportMessage::*;
use crate::lang::{WordData, WordGroup};
use crate::navigation::{NavigatedPage, Page, RootMessage};
use crate::styling::*;
use iced::widget::button::danger;
use iced::widget::container::success;
use iced::widget::{
    Row, button, checkbox, column, container, progress_bar, row, rule, text, text_input,
};
use iced::widget::{scrollable, space};
use iced::{Element, Task};
use iced_core::Alignment::Center;
use iced_core::Length::Fill;
use iced_core::Padding;
use rfd::AsyncFileDialog;
use rusqlite::Connection;
use std::fs::{File, OpenOptions};
use std::io::BufReader;
use std::path::PathBuf;
use std::sync::Arc;
use parking_lot::Mutex;
use tokio::task::spawn_blocking;
use zip::ZipArchive;

const DEFAULT_FIELDS: [&str; 6] = ["key", "value", "tags", "reading", "context", "description"];

#[derive(Clone)]
pub struct ImportState {
    state: Arc<Mutex<AppState>>,
    path: Option<PathBuf>,
    import_data: Option<ImportData>,
    selected_index: usize,
    selected_property: Option<String>,
    custom_property_name: String,
    skip_empty: bool,
    separator: String,
    progress: Option<Arc<Mutex<f32>>>,
}

#[derive(Clone)]
pub enum ImportMessage {
    Back,
    SelectFile,
    UpdateFile(PathBuf),
    UpdateImport(ImportData),
    NextGroup,
    PreviousGroup,
    OpenPropertyMapper(String),
    EditCustomProperty(String),
    SetupCustomProperty,
    SetupDefaultProperty(String),
    SetupDirect,
    RemoveMapping(String),
    SwitchSkipEmpty(bool),
    EditSeparator(String),
    StartImport,
    NextProgress,
    ImportFinished,
}

impl NavigatedPage<ImportMessage> for ImportState {
    fn navigate(&mut self, message: &ImportMessage) -> Option<Page> {
        if self.progress.is_some() {
            return None;
        }

        if let Back = message {
            return Some(Page::PreviousPage);
        }
        None
    }

    fn navigated(&mut self) {}

    fn update(&mut self, message: ImportMessage) -> Task<RootMessage> {
        match message {
            Back => {}
            UpdateFile(path) => {
                self.path = Some(path);
                self.import_data = None;
                return self.load_package();
            }
            SelectFile => return Self::select_import_file(),
            UpdateImport(import) => {
                self.selected_index = 0;
                self.import_data = Some(import);
            }
            NextGroup => {
                self.selected_index += 1;
                self.selected_property = None;
                self.custom_property_name.clear();
            }
            PreviousGroup => {
                self.selected_index -= 1;
                self.selected_property = None;
                self.custom_property_name.clear();
            }
            OpenPropertyMapper(prop) => self.selected_property = Some(prop),
            EditCustomProperty(prop_name) => self.custom_property_name = prop_name,
            SetupCustomProperty => {
                self.setup_mapping(self.custom_property_name.clone());
            }
            SetupDefaultProperty(prop_name) => {
                self.setup_mapping(prop_name);
            }
            SetupDirect => {
                self.setup_mapping(self.selected_property.clone().unwrap());
            }
            RemoveMapping(pro_name) => {
                self.remove_mapping(pro_name);
            }
            SwitchSkipEmpty(skip) => {
                self.skip_empty = skip;
            }
            EditSeparator(separator) => {
                self.separator = separator.clone();
            }
            StartImport => {
                self.progress = Some(Arc::new(Mutex::new(0.0)));
                return Task::batch([self.start_import(), self.next_progress()]);
            }
            NextProgress => {
                if self.progress.is_none() {
                    return Task::none();
                }
                return self.next_progress();
            }
            ImportFinished => {
                self.progress = None;
                let group = &mut self.import_data.as_mut().unwrap().0[self.selected_index];
                group.imported = true;
            }
        }
        Task::none()
    }

    fn view(&self) -> Element<'_, ImportMessage> {
        back_overlay(
            {
                if let Some(value) = &self.progress {
                    column![progress_bar(0f32..=1f32, *value.lock()),]
                        .spacing(QUARTER_SPACING)
                        .into()
                } else {
                    scrollable(
                        column![
                            row![
                                button("Выбрать файл").style(jl_button).on_press(SelectFile),
                                text!("{}", {
                                    if let Some(path) = &self.path {
                                        path.to_string_lossy().to_string()
                                    } else {
                                        "Файл не выбран".to_string()
                                    }
                                })
                            ]
                            .align_y(Center)
                            .spacing(DEFAULT_SPACING),
                            self.selected_group()
                        ]
                        .spacing(DEFAULT_SPACING),
                    )
                    .into()
                }
            },
            Back,
        )
    }
}

impl ImportState {
    fn selected_group(&self) -> Element<'_, ImportMessage> {
        if let Some(data) = &self.import_data {
            let group = &data.0[self.selected_index];
            return column![
                row![
                    self.back_button(),
                    space().width(Fill),
                    text!("{}", group.name.clone()),
                    space().width(Fill),
                    self.next_button()
                ]
                .width(Fill),
                text!("Количество карточек: {}", group.length),
                self.property_mapper(group),
                self.import_settings(),
                self.import_button(group),
            ]
            .spacing(DEFAULT_SPACING)
            .into();
        }
        space().into()
    }

    fn import_button(&self, group: &ImportGroup) -> Element<'_, ImportMessage> {
        if group.imported {
            return container(text!("Группа слов успешно импортирована"))
                .padding(HALF_SPACING)
                .align_x(Center)
                .style(success)
                .width(Fill)
                .into();
        }
        button("Начать импорт")
            .style(cta_button)
            .on_press(StartImport)
            .into()
    }

    fn property_mapper(&self, group: &ImportGroup) -> Element<'_, ImportMessage> {
        column![
            self.property_list(group),
            rule::horizontal(2),
            self.property_selector()
        ]
        .spacing(HALF_SPACING)
        .into()
    }

    fn property_list(&self, group: &ImportGroup) -> Element<'_, ImportMessage> {
        let mut row = Row::new();
        for prop in &group.fields {
            let primary_button_name = (*prop).clone();
            if let Some(mapped) = group.mapping.get(prop.clone().as_str()) {
                row = row.push(
                    column![
                        button(text!("{}", primary_button_name)),
                        "↕",
                        button(text!("{}", mapped))
                            .style(danger)
                            .on_press(RemoveMapping(mapped.as_str().to_string())),
                    ]
                    .align_x(Center),
                );
            } else {
                row = row.push(
                    button(text!("{}", primary_button_name))
                        .on_press(OpenPropertyMapper(prop.clone())),
                );
            }
        }
        scrollable(row.spacing(HALF_SPACING).padding(Padding {
            top: 0.0,
            right: 0.0,
            bottom: DEFAULT_SPACING,
            left: 0.0,
        }))
        .horizontal()
        .into()
    }

    fn property_selector(&self) -> Element<'_, ImportMessage> {
        if self.selected_property.is_none() {
            return space().into();
        }
        column![
            self.available_properties(),
            button("Добавить напрямую").on_press(SetupDirect),
            row![
                text_input("Пользовательское поле", &self.custom_property_name)
                    .on_input(EditCustomProperty),
                button("Установить привязку").on_press(SetupCustomProperty)
            ]
            .spacing(QUARTER_SPACING)
        ]
        .spacing(HALF_SPACING)
        .into()
    }

    fn available_properties(&self) -> Element<'_, ImportMessage> {
        let mut row = Row::new();

        for default in DEFAULT_FIELDS {
            // if group.mapping.values().any(|name| name == default) {
            //     continue;
            // }
            row = row.push(
                button(text!("{default}")).on_press(SetupDefaultProperty(default.to_string())),
            );
        }
        row.spacing(HALF_SPACING).into()
    }

    fn import_settings(&self) -> Element<'_, ImportMessage> {
        column![
            checkbox(self.skip_empty)
                .label("Пропускать пустые строки")
                .on_toggle(SwitchSkipEmpty),
            column![
                text!("Разделитель при множественном объединении"),
                text_input("", &self.separator).on_input(EditSeparator)
            ]
        ]
        .spacing(DEFAULT_SPACING)
        .into()
    }

    fn back_button(&self) -> Element<'_, ImportMessage> {
        let mut button = button("←").style(button::text);
        if self.selected_index > 0 {
            button = button.on_press(PreviousGroup);
        }
        button.into()
    }

    fn next_button(&self) -> Element<'_, ImportMessage> {
        let mut button = button("→").style(button::text);
        if self.selected_index < self.import_data.as_ref().unwrap().0.len() - 1 {
            button = button.on_press(NextGroup);
        }
        button.into()
    }
}

impl ImportState {
    pub fn new(state: Arc<Mutex<AppState>>) -> ImportState {
        ImportState {
            state,
            path: None,
            import_data: None,
            selected_index: 0,
            selected_property: None,
            custom_property_name: "".to_string(),
            skip_empty: true,
            separator: ", ".to_string(),
            progress: None,
        }
    }

    fn select_import_file() -> Task<RootMessage> {
        Task::perform(
            async {
                let file = AsyncFileDialog::new()
                    .add_filter("Anki", &["apkg"])
                    .pick_file()
                    .await;
                if let Some(file) = file {
                    return Some(file.path().into());
                }
                None
            },
            |result| {
                if let Some(path) = result {
                    RootMessage::Import(UpdateFile(path))
                } else {
                    RootMessage::None
                }
            },
        )
    }

    pub fn load_package(&self) -> Task<RootMessage> {
        let path = self.path.clone().unwrap();
        Task::perform(
            async {
                spawn_blocking(move || {
                    Self::extract_import_file(path)?;
                    let data = Self::read_import_file()?;
                    Ok(data)
                })
                .await
                .unwrap()
            },
            |result: Result<ImportData, ()>| {
                if let Ok(import) = result {
                    RootMessage::Import(UpdateImport(import))
                } else {
                    RootMessage::None
                }
            },
        )
    }

    fn extract_import_file(path: PathBuf) -> Result<(), ()> {
        let file = File::open(path).map_err(|_| ())?;
        let reader = BufReader::new(file);
        let mut archive = ZipArchive::new(reader).map_err(|_| ())?;
        let temp_file_path = app_data_dir().join("import");
        let mut temp_file = OpenOptions::new()
            .write(true)
            .create(true)
            .truncate(true)
            .open(&temp_file_path)
            .map_err(|_| ())?;
        if let Ok(mut file) = archive.by_name("collection.anki21") {
            std::io::copy(&mut file, &mut temp_file).map_err(|_| ())?;
            Ok(())
        } else if let Ok(mut file) = archive.by_name("collection.anki2") {
            std::io::copy(&mut file, &mut temp_file).map_err(|_| ())?;
            Ok(())
        } else {
            Err(())
        }
    }
    fn read_import_file() -> Result<ImportData, ()> {
        let temp_file_path = app_data_dir().join("import");
        let connection = Connection::open(&temp_file_path).map_err(|_| ())?;
        let data = load_groups(&connection);
        Ok(data)
    }
    fn setup_mapping(&mut self, property_name: String) {
        let group = self.import_data.as_mut().unwrap();
        let group = &mut group.0[self.selected_index];
        group
            .mapping
            .insert(self.selected_property.clone().unwrap(), property_name);
        self.selected_property = None;
        self.custom_property_name.clear();
    }
    fn remove_mapping(&mut self, property_name: String) {
        let group = self.import_data.as_mut().unwrap();
        let group = &mut group.0[self.selected_index];
        let remove_key = group
            .mapping
            .keys()
            .find(|key| group.mapping[*key] == property_name)
            .unwrap();
        group.mapping.remove(remove_key.clone().as_str());
        self.selected_property = None;
        self.custom_property_name.clear();
    }

    fn start_import(&self) -> Task<RootMessage> {
        let state = self.state.clone();
        let separator = self.separator.clone();
        let skip_empty = self.skip_empty;
        let group = self.import_data.as_ref().unwrap().0[self.selected_index].clone();
        let progress = self.progress.clone().unwrap();
        println!("{}", group.name);
        Task::perform(
            async move {
                spawn_blocking(move || {
                    let temp_file_path = app_data_dir().join("import");
                    let connection = Connection::open(&temp_file_path).map_err(|_| ())?;
                    let import_list = get_words_of_group(&connection, group.id);
                    let mut words_list = Vec::with_capacity(import_list.len());

                    let map_indices = Self::get_mapping_indices(&group);
                    let mut group_entity = WordGroup {
                        id: 0.into(),
                        name: group.name.clone(),
                    };
                    {
                        let state = state.lock();
                        add_group(&mut group_entity, &state.connection);
                    }

                    let group_id = group_entity.id;
                    for import in import_list {
                        let mut word = WordData::new();

                        word.tags = import.tags.trim().replace(" ", ", ");
                        word.group_id = group_id;
                        for (dest, indices) in &map_indices {
                            let collected_string = Self::collect_strings(
                                &import.fields,
                                indices,
                                &separator,
                                skip_empty,
                            );
                            match dest.as_str() {
                                "key" => {
                                    word.key = collected_string;
                                }
                                "value" => {
                                    word.value = collected_string;
                                }
                                "tags" => {
                                    word.tags = collected_string;
                                }
                                &_ => {
                                    word.additional.insert((*dest).clone(), collected_string);
                                }
                            }
                        }

                        words_list.push(word);
                    }

                    let mut state = state.lock();
                    let connection = &mut state.connection;
                    let mut index = 0;
                    let total_len = words_list.len() as f32 / 1024.0;
                    for word in &mut words_list.chunks_mut(1024) {
                        add_words(word, connection);
                        index += 1;
                        let mut progress = progress.lock();
                        *progress = index as f32 / total_len;
                    }

                    state.dictionary.append(&mut words_list);
                    state.word_groups.push(group_entity);

                    Ok(())
                })
                .await
                .unwrap()
            },
            |_: Result<(), ()>| RootMessage::Import(ImportFinished),
        )
    }

    fn get_mapping_indices(group: &ImportGroup) -> Vec<(String, Vec<usize>)> {
        let mut final_props = group.mapping.values().cloned().collect::<Vec<_>>();
        final_props.sort();
        final_props.dedup();
        let mut result = final_props
            .iter()
            .map(|name| (name.clone(), Vec::<usize>::with_capacity(1)))
            .collect::<Vec<_>>();
        for (key, endpoint) in &group.mapping {
            let property_index = group
                .fields
                .iter()
                .position(|f| f == key)
                .expect("Unknown property");
            let group_index = result
                .iter()
                .position(|(name, _)| name == endpoint)
                .unwrap();
            result[group_index].1.push(property_index);
        }

        result
    }

    fn collect_strings(
        properties: &[String],
        indices: &[usize],
        separator: &str,
        skip_empty: bool,
    ) -> String {
        let mut working_words = Vec::with_capacity(indices.len());
        for index in indices {
            let str = &properties[*index];
            if skip_empty && str.is_empty() {
                continue;
            }
            working_words.push(str.as_str());
        }

        working_words.join(separator)
    }

    fn next_progress(&self) -> Task<RootMessage> {
        Task::perform(
            async {
                tokio::time::sleep(tokio::time::Duration::from_millis(100)).await;
            },
            |_| RootMessage::Import(NextProgress),
        )
    }
}
