use nu_plugin::{EngineInterface, EvaluatedCall, PluginCommand, SimplePluginCommand};
use nu_protocol::{
    Category, LabeledError, Signature, SyntaxShape, Type, Value
};
use fluent::{FluentBundle, FluentResource, FluentArgs, FluentValue};
use unic_langid::LanguageIdentifier;
use crate::FluentPlugin;

pub struct Localize;

impl SimplePluginCommand for Localize {
    type Plugin = FluentPlugin;

    fn name(&self) -> &str {
        "fluent-localize"
    }

    fn signature(&self) -> Signature {
        Signature::build(PluginCommand::name(self))
            .input_output_type(Type::Any, Type::String)
            .required("message_id", SyntaxShape::String, "Message ID to localize")
            .required("locale", SyntaxShape::String, "Locale code (e.g., en-US)")
            .named(
                "bundle",
                SyntaxShape::Record(vec![]),
                "Pre-loaded message bundle",
                Some('b'),
            )
            .named(
                "args",
                SyntaxShape::Record(vec![]),
                "Arguments for message interpolation",
                Some('a'),
            )
            .named(
                "files",
                SyntaxShape::List(Box::new(SyntaxShape::Filepath)),
                "FTL files to load",
                Some('f'),
            )
            .switch("fallback", "Return message ID if translation not found", Some('F'))
            .category(Category::Strings)
    }

    fn description(&self) -> &str {
        "Localize a message using Fluent translation system with hierarchical fallback"
    }

    fn examples(&self) -> Vec<nu_protocol::Example<'_>> {
        vec![
            nu_protocol::Example {
                description: "Localize a simple message",
                example: "fluent-localize welcome-message en-US",
                result: None,
            },
            nu_protocol::Example {
                description: "Localize with arguments",
                example: "fluent-localize user-greeting en-US --args {name: 'Alice'}",
                result: None,
            },
            nu_protocol::Example {
                description: "Use pre-loaded bundle",
                example: "let bundle = (fluent-parse messages.ftl); fluent-localize welcome-message en-US --bundle $bundle",
                result: None,
            },
        ]
    }

    fn run(
        &self,
        _plugin: &Self::Plugin,
        _engine: &EngineInterface,
        call: &EvaluatedCall,
        _input: &Value,
    ) -> Result<Value, LabeledError> {
        let message_id: String = call.req(0)?;
        let locale_code: String = call.req(1)?;

        // Parse locale
        let locale: LanguageIdentifier = locale_code.parse()
            .map_err(|e| LabeledError::new("Invalid locale").with_label(format!("Invalid locale '{}': {}", locale_code, e), call.head))?;

        // Create FluentBundle
        let mut bundle = FluentBundle::new(vec![locale.clone()]);

        // Load messages from various sources
        if let Some(bundle_value) = call.get_flag("bundle")? {
            // Use pre-loaded bundle (from cache or previous parse)
            load_from_bundle_value(&mut bundle, bundle_value)?;
        } else if let Some(files_value) = call.get_flag("files")? {
            // Load from FTL files
            let files = extract_file_list(files_value)?;
            load_from_files(&mut bundle, &files)?;
        } else {
            return Err(LabeledError::new("Missing argument").with_label("Must provide either --bundle or --files", call.head));
        }

        // Prepare arguments for interpolation
        let fluent_args = if let Some(args_value) = call.get_flag("args")? {
            Some(convert_to_fluent_args(args_value)?)
        } else {
            None
        };

        // Get the message
        let msg = match bundle.get_message(&message_id) {
            Some(msg) => msg,
            None => {
                if call.has_flag("fallback").unwrap_or(false) {
                    // Return message ID as fallback
                    return Ok(Value::string(format!("[[{}]]", message_id), call.head));
                } else {
                    return Err(LabeledError::new("Message not found").with_label(format!("Message '{}' not found in locale '{}'", message_id, locale_code), call.head));
                }
            }
        };

        // Format the message
        let pattern = msg.value()
            .ok_or_else(|| LabeledError::new("Message has no value").with_label(format!("Message '{}' has no value", message_id), call.head))?;

        let mut errors = vec![];
        let formatted = bundle.format_pattern(
            pattern,
            fluent_args.as_ref(),
            &mut errors
        );

        // Handle formatting errors
        if !errors.is_empty() {
            let error_msgs: Vec<String> = errors.iter()
                .map(|e| format!("{:?}", e))
                .collect();
            return Err(LabeledError::new("Formatting error").with_label(format!(
                "Formatting errors for message '{}': {}",
                message_id,
                error_msgs.join(", ")
            ), call.head));
        }

        Ok(Value::string(formatted.to_string(), call.head))
    }
}

fn load_from_bundle_value(
    bundle: &mut FluentBundle<FluentResource>,
    bundle_value: Value,
) -> Result<(), LabeledError> {
    match bundle_value {
        Value::Record { val, .. } => {
            // Extract messages from parsed bundle record
            if let Some(messages_value) = val.get("messages") {
                load_messages_from_value(bundle, messages_value)?;
            }
        }
        _ => return Err(LabeledError::new("Invalid bundle").with_label("Bundle must be a record", nu_protocol::Span::unknown())),
    }
    Ok(())
}

fn load_messages_from_value(
    bundle: &mut FluentBundle<FluentResource>,
    messages_value: &Value,
) -> Result<(), LabeledError> {
    match messages_value {
        Value::List { vals, .. } => {
            for message_val in vals {
                if let Value::Record { val, .. } = message_val {
                    if let (Some(id_val), Some(text_val)) = (val.get("id"), val.get("text")) {
                        let id = value_to_string(id_val)?;
                        let text = value_to_string(text_val)?;

                        // Create a minimal FTL resource from the message
                        let ftl_content = format!("{} = {}", id, text);
                        let resource = FluentResource::try_new(ftl_content)
                            .map_err(|_| LabeledError::new("Invalid FTL").with_label(format!("Invalid FTL for message '{}'", id), nu_protocol::Span::unknown()))?;

                        bundle.add_resource(resource)
                            .map_err(|_| LabeledError::new("Failed to add message").with_label(format!("Failed to add message '{}'", id), nu_protocol::Span::unknown()))?;
                    }
                }
            }
        }
        _ => return Err(LabeledError::new("Invalid messages").with_label("Messages must be a list", nu_protocol::Span::unknown())),
    }
    Ok(())
}

fn load_from_files(
    bundle: &mut FluentBundle<FluentResource>,
    files: &[String],
) -> Result<(), LabeledError> {
    for file_path in files {
        let content = std::fs::read_to_string(file_path)
            .map_err(|e| LabeledError::new("Read error").with_label(format!("Failed to read '{}': {}", file_path, e), nu_protocol::Span::unknown()))?;

        let resource = FluentResource::try_new(content)
            .map_err(|e| LabeledError::new("Invalid FTL").with_label(format!("Invalid FTL in '{}': {:?}", file_path, e), nu_protocol::Span::unknown()))?;

        bundle.add_resource(resource)
            .map_err(|e| LabeledError::new("Load error").with_label(format!("Failed to load '{}': {:?}", file_path, e), nu_protocol::Span::unknown()))?;
    }
    Ok(())
}

fn extract_file_list(files_value: Value) -> Result<Vec<String>, LabeledError> {
    match files_value {
        Value::List { vals, .. } => {
            vals.iter()
                .map(|v| value_to_string(v))
                .collect::<Result<Vec<_>, _>>()
        }
        _ => Err(LabeledError::new("Invalid files").with_label("Files must be a list of strings", nu_protocol::Span::unknown())),
    }
}

fn convert_to_fluent_args(args_value: Value) -> Result<FluentArgs<'static>, LabeledError> {
    let mut fluent_args = FluentArgs::new();

    match args_value {
        Value::Record { val, .. } => {
            for (key, value) in val.iter() {
                let fluent_value = match value {
                    Value::String { val, .. } => FluentValue::from(val.clone()),
                    Value::Int { val, .. } => FluentValue::from(*val as f64),
                    Value::Float { val, .. } => FluentValue::from(*val),
                    _ => return Err(LabeledError::new("Unsupported argument type").with_label(format!(
                        "Unsupported argument type for '{}': {:?}",
                        key,
                        value.get_type()
                    ), nu_protocol::Span::unknown())),
                };
                fluent_args.set(key.clone(), fluent_value);
            }
        }
        _ => return Err(LabeledError::new("Invalid arguments").with_label("Arguments must be a record", nu_protocol::Span::unknown())),
    }

    Ok(fluent_args)
}

fn value_to_string(value: &Value) -> Result<String, LabeledError> {
    match value {
        Value::String { val, .. } => Ok(val.clone()),
        Value::Int { val, .. } => Ok(val.to_string()),
        Value::Float { val, .. } => Ok(val.to_string()),
        Value::Bool { val, .. } => Ok(val.to_string()),
        _ => Err(LabeledError::new("Type conversion error").with_label(
            format!("Cannot convert {:?} to string", value.get_type()),
            nu_protocol::Span::unknown()
        )),
    }
}