use crate::ast::{LineInfo, Type};
use crate::env::{CallArg, Environment, Value};
use crate::eval::{EvalError, EvalResult};
use std::io::{self, Write};
use std::rc::Rc;

pub trait IoBridge {
    fn write_str(&mut self, content: &str) -> io::Result<()>;
    fn flush(&mut self) -> io::Result<()>;
    fn read_line(&mut self, buffer: &mut String) -> io::Result<()>;
}

struct StdIoBridge;

impl IoBridge for StdIoBridge {
    fn write_str(&mut self, content: &str) -> io::Result<()> {
        io::stdout().write_all(content.as_bytes())
    }

    fn flush(&mut self) -> io::Result<()> {
        io::stdout().flush()
    }

    fn read_line(&mut self, buffer: &mut String) -> io::Result<()> {
        io::stdin().read_line(buffer).map(|_| ())
    }
}

pub fn native_unveil(
    env: &mut Environment,
    args: Vec<CallArg>,
    line: Option<LineInfo>,
) -> Result<EvalResult, EvalError> {
    let mut io = StdIoBridge;
    native_unveil_with_io(env, args, line, &mut io)
}

fn map_io_error(message: &str, line: &Option<LineInfo>) -> EvalError {
    EvalError::InvalidOperation(message.to_string(), line.clone())
}

pub(crate) fn native_unveil_with_io(
    _env: &mut Environment,
    args: Vec<CallArg>,
    line: Option<LineInfo>,
    io: &mut dyn IoBridge,
) -> Result<EvalResult, EvalError> {
    if args.is_empty() {
        return Err(EvalError::InvalidOperation(
            "unveil() requires at least 1 argument".to_string(),
            line,
        ));
    }

    let outputs: Result<Vec<String>, EvalError> = args
        .iter()
        .map(|arg| format_eval_result(&arg.value, &line))
        .collect();

    let output_str = outputs?.join("");
    io.write_str(&output_str)
        .map_err(|_| map_io_error("Failed to write unveil output", &line))?;
    io.write_str("\n")
        .map_err(|_| map_io_error("Failed to write unveil newline", &line))?;
    Ok(EvalResult::abyss())
}

pub fn native_summon(
    env: &mut Environment,
    args: Vec<CallArg>,
    line: Option<LineInfo>,
) -> Result<EvalResult, EvalError> {
    let mut io = StdIoBridge;
    native_summon_with_io(env, args, line, &mut io)
}

pub(crate) fn native_summon_with_io(
    _env: &mut Environment,
    args: Vec<CallArg>,
    line: Option<LineInfo>,
    io: &mut dyn IoBridge,
) -> Result<EvalResult, EvalError> {
    if args.len() != 1 {
        return Err(EvalError::InvalidOperation(
            "summon() requires exactly 1 argument (prompt)".to_string(),
            line,
        ));
    }

    let prompt = match &args[0].value {
        EvalResult::Data(Value::Rune(r)) => r.as_ref(),
        _ => {
            return Err(EvalError::TypeError(
                "summon() argument must be a Rune (prompt)".to_string(),
                line,
            ));
        }
    };

    io.write_str(prompt)
        .and_then(|_| io.flush())
        .map_err(|_| map_io_error("Failed to flush stdout", &line))?;

    let mut input = String::new();
    io.read_line(&mut input)
        .map_err(|_| map_io_error("Failed to read input", &line))?;

    Ok(EvalResult::data(Value::Rune(Rc::new(
        input.trim().to_string(),
    ))))
}

pub(crate) fn format_eval_result(
    value: &EvalResult,
    line: &Option<LineInfo>,
) -> Result<String, EvalError> {
    match value {
        EvalResult::Data(inner) => format_value(inner, line),
        EvalResult::Artifact(handle) => format_artifact(handle, line),
        EvalResult::Revealed(_) => Err(EvalError::InvalidOperation(
            "Cannot unveil a Revealed value (control flow construct)".to_string(),
            line.clone(),
        )),
        EvalResult::Resume(_) => Err(EvalError::InvalidOperation(
            "Cannot unveil a Resume value (control flow construct)".to_string(),
            line.clone(),
        )),
        EvalResult::Eject(_) => Err(EvalError::InvalidOperation(
            "Cannot unveil an Eject value (control flow construct)".to_string(),
            line.clone(),
        )),
    }
}

fn glyph_label(var_type: &Type) -> String {
    match var_type {
        Type::Arcana => "arcana".to_string(),
        Type::Aether => "aether".to_string(),
        Type::Rune => "rune".to_string(),
        Type::Omen => "omen".to_string(),
        Type::Abyss => "abyss".to_string(),
        Type::Scroll => "scroll".to_string(),
        Type::Lexicon => "lexicon".to_string(),
        Type::Materia => "materia".to_string(),
        Type::Glyph => "glyph".to_string(),
        Type::Artifact(name) => name.clone(),
    }
}

pub(crate) fn format_value(value: &Value, line: &Option<LineInfo>) -> Result<String, EvalError> {
    match value {
        Value::Omen(b) => Ok(if *b { "boon" } else { "hex" }.to_string()),
        Value::Arcana(n) => Ok(n.to_string()),
        Value::Aether(n) => Ok(n.to_string()),
        Value::Rune(s) => Ok(s.replace("\\n", "\n")),
        Value::Abyss => Ok(String::new()),
        Value::Scroll(items) => {
            let parts: Result<Vec<String>, EvalError> = items
                .borrow()
                .iter()
                .map(|item| format_value(item, line))
                .collect();
            Ok(format!("[{}]", parts?.join(", ")))
        }
        Value::Lexicon(entries) => {
            let mut pieces = Vec::new();
            for (key, val) in entries.borrow().iter() {
                let formatted_value = format_value(val, line)?;
                pieces.push(format!("\"{}\": {}", key, formatted_value));
            }
            Ok(format!("{{{}}}", pieces.join(", ")))
        }
        Value::Glyph(var_type) => Ok(glyph_label(var_type)),
        Value::Artifact(handle) => format_artifact(handle, line),
    }
}

pub(crate) fn format_artifact(
    handle: &crate::env::ArtifactHandle,
    line: &Option<LineInfo>,
) -> Result<String, EvalError> {
    let borrowed = handle.borrow();
    let mut pieces = Vec::new();
    for field in &borrowed.field_order {
        if let Some(value) = borrowed.fields.get(field) {
            let formatted = format_value(value, line)?;
            pieces.push(format!("{}: {}", field, formatted));
        }
    }
    Ok(format!(
        "{} {{ {} }}",
        borrowed.type_name,
        pieces.join(", ")
    ))
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::env::{ArtifactHandle, ArtifactValue};
    use std::cell::RefCell;
    use std::collections::{HashMap, VecDeque};
    use std::rc::Rc;

    #[derive(Default)]
    struct MockIo {
        writes: Vec<String>,
        reads: VecDeque<Result<String, &'static str>>,
        fail_write: bool,
        fail_flush: bool,
    }

    impl MockIo {
        fn with_reads(lines: &[&str]) -> Self {
            let mut io = MockIo::default();
            for line in lines {
                io.reads.push_back(Ok((*line).to_string()));
            }
            io
        }

        fn with_write_failure() -> Self {
            MockIo {
                fail_write: true,
                ..Default::default()
            }
        }

        fn with_flush_failure() -> Self {
            MockIo {
                fail_flush: true,
                ..Default::default()
            }
        }

        fn push_error(&mut self, msg: &'static str) {
            self.reads.push_back(Err(msg));
        }
    }

    impl IoBridge for MockIo {
        fn write_str(&mut self, content: &str) -> io::Result<()> {
            if self.fail_write {
                return Err(io::Error::other("write failure"));
            }
            self.writes.push(content.to_string());
            Ok(())
        }

        fn flush(&mut self) -> io::Result<()> {
            if self.fail_flush {
                return Err(io::Error::other("flush failure"));
            }
            Ok(())
        }

        fn read_line(&mut self, buffer: &mut String) -> io::Result<()> {
            match self.reads.pop_front() {
                Some(Ok(line)) => {
                    buffer.push_str(&line);
                    Ok(())
                }
                Some(Err(msg)) => Err(io::Error::other(msg)),
                None => Err(io::Error::new(
                    io::ErrorKind::UnexpectedEof,
                    "no input queued",
                )),
            }
        }
    }

    fn arg(value: EvalResult) -> CallArg {
        CallArg {
            value,
            var_name: None,
        }
    }

    fn artifact_handle() -> ArtifactHandle {
        let mut fields = HashMap::new();
        fields.insert("name".to_string(), Value::Rune(Rc::new("Alya".to_string())));
        fields.insert("level".to_string(), Value::Arcana(7));
        let field_order = vec!["name".to_string(), "level".to_string()];
        Rc::new(RefCell::new(ArtifactValue {
            type_name: "Player".to_string(),
            fields,
            field_order,
        }))
    }

    #[test]
    fn native_unveil_with_io_writes_all_arguments() {
        let mut env = Environment::new();
        let args = vec![
            arg(EvalResult::data(Value::Omen(true))),
            arg(EvalResult::data(Value::Rune(Rc::new("hex".to_string())))),
        ];
        let mut io = MockIo::default();
        let result =
            native_unveil_with_io(&mut env, args, None, &mut io).expect("unveil should succeed");
        assert!(matches!(result, EvalResult::Data(Value::Abyss)));
        assert_eq!(io.writes.join(""), "boonhex\n");
    }

    #[test]
    fn native_unveil_with_io_rejects_control_flow() {
        let mut env = Environment::new();
        let args = vec![arg(EvalResult::Revealed(Box::new(EvalResult::abyss())))];
        let mut io = MockIo::default();
        let err = native_unveil_with_io(&mut env, args, None, &mut io)
            .expect_err("control flow values should error");
        if let EvalError::InvalidOperation(msg, _) = err {
            assert!(msg.contains("Revealed"));
        } else {
            panic!("expected invalid operation, got {:?}", err);
        }
    }

    #[test]
    fn native_unveil_with_io_propagates_write_errors() {
        let mut env = Environment::new();
        let args = vec![arg(EvalResult::data(Value::Arcana(1)))];
        let mut io = MockIo::with_write_failure();
        assert!(native_unveil_with_io(&mut env, args, None, &mut io).is_err());
    }

    #[test]
    fn native_summon_with_io_returns_trimmed_input() {
        let mut env = Environment::new();
        let args = vec![arg(EvalResult::data(Value::Rune(Rc::new("?".to_string()))))];
        let mut io = MockIo::with_reads(&["mage answer\n"]);
        let result =
            native_summon_with_io(&mut env, args, None, &mut io).expect("summon should succeed");
        match result {
            EvalResult::Data(Value::Rune(text)) => assert_eq!(text.as_ref(), "mage answer"),
            other => panic!("expected rune result, got {:?}", other),
        }
        assert_eq!(io.writes.join(""), "?");
    }

    #[test]
    fn native_summon_with_io_errors_on_read_failure() {
        let mut env = Environment::new();
        let args = vec![arg(EvalResult::data(Value::Rune(Rc::new("?".to_string()))))];
        let mut io = MockIo::with_reads(&[]);
        io.push_error("read failure");
        assert!(native_summon_with_io(&mut env, args, None, &mut io).is_err());
    }

    #[test]
    fn native_summon_with_io_errors_on_flush_failure() {
        let mut env = Environment::new();
        let args = vec![arg(EvalResult::data(Value::Rune(Rc::new("?".to_string()))))];
        let mut io = MockIo::with_flush_failure();
        assert!(native_summon_with_io(&mut env, args, None, &mut io).is_err());
    }

    #[test]
    fn format_value_handles_collections_and_artifacts() {
        let scroll = Value::Scroll(Rc::new(RefCell::new(vec![
            Value::Arcana(1),
            Value::Arcana(2),
        ])));
        let mut lex_entries = HashMap::new();
        lex_entries.insert("alpha".to_string(), Value::Omen(true));
        let lexicon = Value::Lexicon(Rc::new(RefCell::new(lex_entries)));
        let artifact = Value::Artifact(artifact_handle());

        assert_eq!(format_value(&scroll, &None).unwrap(), "[1, 2]");
        assert!(
            format_value(&lexicon, &None)
                .unwrap()
                .contains("\"alpha\": boon")
        );
        assert!(
            format_value(&artifact, &None)
                .unwrap()
                .starts_with("Player {")
        );
    }
}
