use std::{fs::File, io::Read, path::PathBuf, time::SystemTime};

use crate::{debug, error, warn};
use ab_glyph::FontRef;
use nu_plugin::EvaluatedCall;
use nu_protocol::{LabeledError, Span, Value};

use crate::FontFamily;

use super::{
    converter::make_image,
    palette::{Palette, PaletteColorOverrides, strhex_to_rgba},
};

/// Converts ANSI escape sequences to an image (PNG).
///
/// # Errors
/// Returns an error if:
/// - The input is not a string or binary
/// - The width parameter is invalid
/// - Image creation or file writing fails
pub fn ansi_to_image(
    engine: &nu_plugin::EngineInterface,
    call: &EvaluatedCall,
    input: &Value,
) -> Result<Value, LabeledError> {
    let i: &[u8] = match input {
        Value::String {
            val,
            internal_span: _,
            ..
        } => val.as_bytes(),
        Value::Binary {
            val,
            internal_span: _,
            ..
        } => val,
        _ => {
            return Err(make_params_err(
                "cannot read input as binary data (maybe its empty)".to_string(),
                input.span(),
            ));
        }
    };
    #[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
    // Width is expected to be positive and within u32 range
    let size = match call.get_flag_value("width") {
        Some(val) => val.as_int().ok().map(|value| value as u32),
        _ => None,
    };
    let font: FontFamily<'_> = resolve_font(call);
    let out_path = call.opt::<String>(0);

    let out = if let Ok(Some(path)) = out_path {
        debug!("received output name `{}`", path);
        if let Ok(value) = engine.get_current_dir() {
            let mut absolute = PathBuf::from(value);
            absolute.extend(PathBuf::from(path).iter());
            debug!(
                "absolute output name `{}`",
                absolute.to_str().unwrap_or("cannot convert path to string")
            );
            Some(absolute)
        } else {
            warn!("failed to fetch current directories path");
            Some(PathBuf::from(path))
        }
    } else {
        let now = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH);
        let current = engine.get_current_dir().map(PathBuf::from);
        if let (Ok(now), Ok(current)) = (now, current) {
            let current = &mut current.clone();
            current.push(PathBuf::from(format!("nu-image-{}.png", now.as_secs())));
            Some(current.to_owned())
        } else {
            None
        }
    };
    if out.is_none() {
        return Err(make_params_err(
            "cannot use time stamp as the file name timestamp please provide output path explicitly".to_string(),
            call.head,
        ));
    }
    let theme = match call
        .get_flag_value("theme")
        .map(|i| i.as_str().map(std::string::ToString::to_string))
    {
        Some(Ok(name)) => {
            if let Some(theme) = Palette::from_name(&name) {
                theme
            } else {
                error!("No theme found that matches the given name");
                Palette::default()
            }
        }
        _ => Palette::default(),
    };
    let theme = load_custom_theme(call, &theme);

    let path = out
        .ok_or_else(|| make_params_err("Failed to determine output path".to_string(), call.head))?;

    if let Err(e) = make_image(path.as_path(), font, size, i, theme) {
        return Err(make_params_err(
            format!("Failed to save image: {e}"),
            call.head,
        ));
    }

    Ok(Value::string(
        path.to_str().unwrap_or("error reading path").to_owned(),
        call.head,
    ))
}

fn resolve_font(call: &EvaluatedCall) -> FontFamily<'static> {
    let mut font: FontFamily<'static> = match call.get_flag_value("font").map(|value| match value {
        Value::String { val, .. } => Some(FontFamily::from_name(val.as_str())),
        _ => None,
    }) {
        Some(value) => value.unwrap_or_default(),
        None => FontFamily::default(),
    };
    // TODO custom fonts disabled for now
    if let Some(path) = call.get_flag_value("font-regular") {
        let buffer = load_file(&path);
        font.regular = FontRef::try_from_slice(buffer).unwrap();
    }
    if let Some(path) = call.get_flag_value("font-bold") {
        let buffer = load_file(&path);
        font.bold = FontRef::try_from_slice(buffer).unwrap();
    }
    if let Some(path) = call.get_flag_value("font-italic") {
        let buffer = load_file(&path);
        font.italic = FontRef::try_from_slice(buffer).unwrap();
    }
    if let Some(path) = call.get_flag_value("bold-italic") {
        let buffer = load_file(&path);
        font.bold_italic = FontRef::try_from_slice(buffer).unwrap();
    }
    font
}

// fn load_file<'a>(path: Value) -> &'a [u8] {
//     let path = path.as_str().unwrap();
//     let mut file = File::open(PathBuf::from(path)).unwrap();
//     let mut buffer = Vec::new();

//     // read the whole file
//     let _ = file.read_to_end(&mut buffer);
//     buffer.as_slice()
// }

fn load_file<'a>(path: &Value) -> &'a [u8] {
    let path = path.as_str().unwrap();
    let mut file = File::open(PathBuf::from(path)).unwrap();
    let mut buffer: Box<Vec<u8>> = Box::default();
    file.read_to_end(&mut buffer).unwrap();
    Box::leak(buffer)
}

fn make_params_err(text: String, span: Span) -> LabeledError {
    LabeledError::new(text).with_label("faced an error when tried to parse the params", span)
}
fn load_custom_theme(call: &EvaluatedCall, theme: &Palette) -> Palette {
    let overrides = PaletteColorOverrides {
        primary_foreground: read_hex_to_array(call, "custom-theme-fg"),
        primary_background: read_hex_to_array(call, "custom-theme-bg"),
        black: read_hex_to_array(call, "custom-theme-black"),
        red: read_hex_to_array(call, "custom-theme-red"),
        green: read_hex_to_array(call, "custom-theme-green"),
        yellow: read_hex_to_array(call, "custom-theme-yellow"),
        blue: read_hex_to_array(call, "custom-theme-blue"),
        magenta: read_hex_to_array(call, "custom-theme-magenta"),
        cyan: read_hex_to_array(call, "custom-theme-cyan"),
        white: read_hex_to_array(call, "custom-theme-white"),
        bright_black: read_hex_to_array(call, "custom-theme-bright_black"),
        bright_red: read_hex_to_array(call, "custom-theme-bright_red"),
        bright_green: read_hex_to_array(call, "custom-theme-bright_green"),
        bright_yellow: read_hex_to_array(call, "custom-theme-bright_yellow"),
        bright_blue: read_hex_to_array(call, "custom-theme-bright_blue"),
        bright_magenta: read_hex_to_array(call, "custom-theme-bright_magenta"),
        bright_cyan: read_hex_to_array(call, "custom-theme-bright_cyan"),
        bright_white: read_hex_to_array(call, "custom-theme-bright_white"),
    };
    let result = theme.palette().copy_with(&overrides);
    Palette::Custom(Box::new(result))
}

fn read_hex_to_array(call: &EvaluatedCall, name: &str) -> Option<[u8; 4]> {
    if let Some(Value::String { val, .. }) = call.get_flag_value(name) {
        return strhex_to_rgba(&val);
    }
    None
}
