use crate::context::Context;
use crate::context::ContextSession;
use anyhow::Result;
use inquire::{Confirm, Select, Text};
use serde::{Deserialize, Serialize};
use std::fmt::Debug;
use std::hash::Hasher;
use std::hash::Hash;
use std::sync::Arc;

#[derive(Debug, Clone, Hash)]
pub enum QuestionType {
    Text {
        question: String,
        help_message: Option<String>,
        initial_value: Option<String>,
    },
    Confirm {
        question: String,
        help_message: Option<String>,
        default_value: Option<bool>,
    },
    Select {
        question: String,
        options: Vec<String>,
    },
}

impl QuestionType {
    pub fn ask(question: impl Into<String>) -> Self {
        Self::Text {
            question: question.into(),
            help_message: None,
            initial_value: None,
        }
    }

    pub fn select(question: impl Into<String>, options: Vec<impl Into<String>>) -> Self {
        Self::Select {
            question: question.into(),
            options: options.into_iter().map(|o| o.into()).collect(),
        }
    }

    pub fn confirm(question: impl Into<String>) -> Self {
        Self::Confirm {
            question: question.into(),
            help_message: None,
            default_value: Some(true),
        }
    }

    pub fn with_help(mut self, help: impl Into<String>) -> Self {
        match &mut self {
            QuestionType::Text { help_message, .. }
            | QuestionType::Confirm { help_message, .. } => {
                *help_message = Some(help.into());
            }
            _ => {}
        }
        self
    }

    pub fn with_default(mut self, value: impl Into<String>) -> Self {
        if let Self::Text { initial_value, .. } = &mut self {
            *initial_value = Some(value.into());
        }
        self
    }

    pub fn with_confirm_default(mut self, default: bool) -> Self {
        if let Self::Confirm { default_value, .. } = &mut self {
            *default_value = Some(default);
        }
        self
    }

    pub fn get_question(&self) -> &str {
        match self {
            QuestionType::Text { question, .. } => question,
            QuestionType::Select { question, .. } => question,
            QuestionType::Confirm { question, .. } => question,
        }
    }
}

pub type PostQuestionHook = Box<dyn FnMut(&str, &Context, &mut ContextSession) -> Result<bool>>;
pub type QuestionComputedValue = Box<dyn Fn(&ContextSession) -> Result<String>>;
pub type RetryCondition = Box<dyn Fn(&ContextSession) -> Result<bool>>;

pub struct RetryConfig {
    pub max_retries: u32,
    pub should_retry: RetryCondition,
}

impl RetryConfig {
    pub fn new(max_retries: u32, should_retry: RetryCondition) -> Self {
        Self {
            max_retries,
            should_retry,
        }
    }

    pub fn with_default_condition(max_retries: u32) -> Self {
        Self {
            max_retries,
            should_retry: Box::new(|_| Ok(true)), // Always retry on failure
        }
    }
}

impl Default for RetryConfig {
    fn default() -> Self {
        Self::with_default_condition(3)
    }
}

pub struct Question {
    pub question_type: QuestionType,
    pub post_question_hook: Option<PostQuestionHook>,
    pub retry_config: Option<RetryConfig>,
    pub computed_value: Option<Arc<dyn Fn(&mut ContextSession) -> String + Send + Sync>>,
    pub computed_help: Option<Arc<dyn Fn(&mut ContextSession) -> String + Send + Sync>>,

}

impl Hash for Question {
    fn hash<H: Hasher>(&self, state: &mut H) {
        self.question_type.hash(state);
    }
}

impl Question {
    // Concat the question type's question with the question's hash
    pub fn id(&self) -> String {
        use std::hash::{Hash, Hasher};
        let mut hasher = std::collections::hash_map::DefaultHasher::new();
        self.hash(&mut hasher);
        format!(
            "{:?}-{:x}",
            self.question_type.get_question(),
            hasher.finish()
        )
    }

    pub fn ask(question: impl Into<String>) -> Self {
        Self {
            question_type: QuestionType::ask(question),
            post_question_hook: None,
            retry_config: None,
            computed_value: None,
            computed_help: None,
        }
    }

    pub fn select(question: impl Into<String>, options: Vec<impl Into<String>>) -> Self {
        Self {
            question_type: QuestionType::select(question, options),
            post_question_hook: None,
            retry_config: None,
            computed_value: None,
            computed_help: None,
        }
    }

    pub fn with_retry(mut self, config: RetryConfig) -> Self {
        self.retry_config = Some(config);
        self
    }

    pub fn confirm(question: impl Into<String>) -> Self {
        Self {
            retry_config: None,
            question_type: QuestionType::confirm(question),
            post_question_hook: None,
            computed_value: None,
            computed_help: None,
        }
    }

    pub fn with_hook(
        mut self,
        hook: impl FnMut(&str, &Context, &mut ContextSession) -> Result<bool> + 'static,
    ) -> Self {
        self.post_question_hook = Some(Box::new(hook));
        self
    }

    pub fn with_help(mut self, help: impl Into<String>) -> Self {
        match &mut self.question_type {
            QuestionType::Text { help_message, .. }
            | QuestionType::Confirm { help_message, .. } => {
                *help_message = Some(help.into());
            }
            _ => {}
        }
        self
    }

    pub fn with_computed_help<F>(mut self, f: F) -> Self 
    where
        F: Fn(&mut ContextSession) -> String + Send + Sync + 'static 
    {
        self.computed_help = Some(Arc::new(f));
        self
    }

    pub fn with_computed_initial_value<F>(mut self, f: F) -> Self 
    where
        F: Fn(&mut ContextSession) -> String + Send + Sync + 'static 
    {
        self.computed_value = Some(Arc::new(f));
        self
    }

    pub fn with_initial_value(mut self, value: impl Into<String>) -> Self {
        if let QuestionType::Text { initial_value, .. } = &mut self.question_type {
            *initial_value = Some(value.into());
        }
        self
    }

    pub fn with_confirm_default(mut self, default: bool) -> Self {
        if let QuestionType::Confirm { default_value, .. } = &mut self.question_type {
            *default_value = Some(default);
        }
        self
    }

    pub fn get_answer(&self) -> Result<String> {
        match &self.question_type {
            QuestionType::Text {
                question,
                help_message,
                initial_value,
            } => {
                let mut text_prompt = Text::new(question);
                if let Some(help_message) = help_message {
                    text_prompt = text_prompt.with_help_message(help_message);
                }
                if let Some(initial_value) = initial_value {
                    text_prompt = text_prompt.with_initial_value(initial_value);
                }
                text_prompt.prompt().map_err(|e| anyhow::anyhow!(e))
            }
            QuestionType::Select { question, options } => Select::new(question, options.clone())
                .prompt()
                .map_err(|e| anyhow::anyhow!(e)),
            QuestionType::Confirm {
                question,
                help_message,
                default_value,
            } => {
                let mut confirm_prompt = Confirm::new(question);
                if let Some(help_message) = help_message {
                    confirm_prompt = confirm_prompt.with_help_message(help_message);
                }
                if let Some(default_value) = default_value {
                    confirm_prompt = confirm_prompt.with_default(*default_value);
                }
                let result = confirm_prompt.prompt();
                match result {
                    Ok(true) => Ok("true".to_string()),
                    Ok(false) => Ok("false".to_string()),
                    Err(e) => Err(anyhow::anyhow!(e)),
                }
            }
        }
    }


    pub fn get_answer_with_context(&self, context_session: &mut ContextSession) -> Result<String> {
        // Get computed value if available

        let computed_value: Option<String> = self.get_computed_value(context_session);
        let computed_help: Option<String> = self.get_computed_help(context_session);
        match &self.question_type {
            QuestionType::Text {
                question,
                help_message,
                initial_value,
            } => {

                let mut text_prompt = Text::new(question);

                let help_message = if let Some(computed_help) = computed_help {
                    computed_help
                } else if let Some(help_message) = help_message {
                    help_message.clone()
                } else {
                    String::new()
                };

                let initial_text = if let Some(computed_value) = computed_value {
                    computed_value
                } else if let Some(initial_value) = initial_value {
                    initial_value.clone()
                } else {
                    String::new()
                };
                text_prompt = text_prompt.with_help_message(&help_message);
                text_prompt = text_prompt.with_initial_value(&initial_text);
                text_prompt.prompt().map_err(|e| anyhow::anyhow!(e))
            }
            QuestionType::Select { question, options } => Select::new(question, options.clone())
                .prompt()
                .map_err(|e| anyhow::anyhow!(e)),
            QuestionType::Confirm {
                question,
                help_message,
                default_value,
            } => {
                let mut confirm_prompt = Confirm::new(question);
                let help_message = if let Some(computed_help) = computed_help {
                    computed_help
                } else if let Some(help_message) = help_message {
                    help_message.clone()
                } else {
                    String::new()
                };

                confirm_prompt = confirm_prompt.with_help_message(&help_message);
                if let Some(default_value) = default_value {
                    confirm_prompt = confirm_prompt.with_default(*default_value);
                }
                let result = confirm_prompt.prompt();
                match result {
                    Ok(true) => Ok("true".to_string()),
                    Ok(false) => Ok("false".to_string()),
                    Err(e) => Err(anyhow::anyhow!(e)),
                }
            }
        }
    }

    pub fn get_computed_value(&self, context: &mut ContextSession) -> Option<String> {
        if let Some(compute_fn) = &self.computed_value {
            Some(compute_fn(context))
        } else {
            None
        }
    }

    pub fn get_computed_help(&self, context: &mut ContextSession) -> Option<String> {
        if let Some(compute_fn) = &self.computed_help {
            Some(compute_fn(context))
        } else {
            None
        }
    }
}

pub enum InquirePrompt<'a> {
    Select(Select<'a, String>),
    Text(Text<'a>),
}

#[derive(Debug, Serialize, Deserialize)]
pub struct TextOverride {
    pub message_override: Option<String>,
    pub placeholder: Option<String>,
    pub help_message: Option<String>,
    pub default_value: Option<String>,
    pub initial_value: Option<String>,
}

#[derive(Debug, Serialize, Deserialize)]
pub struct SelectOverride {
    pub message_override: Option<String>,
    pub options_override: Option<Vec<String>>,
    pub help_message: Option<String>,
    pub starting_cursor: Option<usize>,
}

#[derive(Debug, Serialize, Deserialize)]
pub enum QuestionParams {
    Text(TextOverride),
    Select(SelectOverride),
}

impl QuestionParams {
    pub fn default_text() -> TextOverride {
        TextOverride {
            message_override: None,
            placeholder: None,
            help_message: None,
            default_value: None,
            initial_value: None,
        }
    }

    pub fn default_select() -> SelectOverride {
        SelectOverride {
            message_override: None,
            options_override: None,
            help_message: None,
            starting_cursor: None,
        }
    }
}
