use super::escape::{escape_command_name, escape_shell_string, escape_variable_name};
use crate::ir::{Command, ShellIR, ShellValue};
use crate::models::{Config, Result};
use std::fmt::Write;

pub struct PosixEmitter {
    #[allow(dead_code)]
    config: Config,
}

impl PosixEmitter {
    pub fn new(config: Config) -> Self {
        Self { config }
    }

    pub fn emit(&self, ir: &ShellIR) -> Result<String> {
        let mut output = String::new();

        // Write the POSIX shell header
        self.write_header(&mut output)?;

        // Write the main content
        self.emit_ir(&mut output, ir, 0)?;

        // Write the footer
        self.write_footer(&mut output)?;

        Ok(output)
    }

    fn write_header(&self, output: &mut String) -> Result<()> {
        writeln!(output, "#!/bin/sh")?;
        writeln!(output, "# Generated by Rash v{}", env!("CARGO_PKG_VERSION"))?;
        writeln!(output, "# POSIX-compliant shell script")?;
        writeln!(output)?;

        // Set strict error handling
        writeln!(output, "set -euf")?;
        writeln!(output, "IFS=' \t\n'")?; // POSIX-compatible IFS setting
        writeln!(output, "export LC_ALL=C")?;
        writeln!(output)?;

        // Include runtime functions if needed
        if self.needs_runtime() {
            self.write_runtime(output)?;
        }

        writeln!(output, "# Main script begins")?;
        writeln!(output, "main() {{")?;

        Ok(())
    }

    fn write_footer(&self, output: &mut String) -> Result<()> {
        writeln!(output, "}}")?;
        writeln!(output)?;
        writeln!(output, "# Cleanup on exit")?;
        writeln!(output, "trap 'rm -rf \"${{TMPDIR:-/tmp}}/rash.$$\"' EXIT")?;
        writeln!(output)?;
        writeln!(output, "# Execute main function")?;
        writeln!(output, "main \"$@\"")?;

        Ok(())
    }

    fn write_runtime(&self, output: &mut String) -> Result<()> {
        writeln!(output, "# Rash runtime functions")?;
        self.write_require_function(output)?;
        self.write_download_function(output)?;
        Ok(())
    }

    fn write_require_function(&self, output: &mut String) -> Result<()> {
        let lines = [
            "rash_require() {",
            "    if ! \"$@\"; then",
            "        echo \"FATAL: Requirement failed: $*\" >&2",
            "        exit 1",
            "    fi",
            "}",
            "",
        ];
        self.write_shell_lines(output, &lines)
    }

    fn write_download_function(&self, output: &mut String) -> Result<()> {
        writeln!(output, "rash_download_verified() {{")?;
        writeln!(output, "    url=\"$1\"; dst=\"$2\"; checksum=\"$3\"")?;
        writeln!(output, "    ")?;

        self.write_download_logic(output)?;
        self.write_checksum_logic(output)?;

        writeln!(output, "}}")?;
        writeln!(output)?;
        Ok(())
    }

    fn write_download_logic(&self, output: &mut String) -> Result<()> {
        let lines = [
            "    if command -v curl >/dev/null 2>&1; then",
            "        curl -fsSL --proto '=https' --tlsv1.2 \"$url\" -o \"$dst\"",
            "    elif command -v wget >/dev/null 2>&1; then",
            "        wget -qO \"$dst\" \"$url\"",
            "    else",
            "        echo \"FATAL: Neither curl nor wget found\" >&2",
            "        return 1",
            "    fi",
            "    ",
        ];
        self.write_shell_lines(output, &lines)
    }

    fn write_checksum_logic(&self, output: &mut String) -> Result<()> {
        let lines = [
            "    if command -v sha256sum >/dev/null 2>&1; then",
            "        echo \"$checksum  $dst\" | sha256sum -c >/dev/null",
            "    elif command -v shasum >/dev/null 2>&1; then",
            "        echo \"$checksum  $dst\" | shasum -a 256 -c >/dev/null",
            "    else",
            "        echo \"FATAL: No checksum utility found\" >&2",
            "        return 1",
            "    fi",
        ];
        self.write_shell_lines(output, &lines)
    }

    fn write_shell_lines(&self, output: &mut String, lines: &[&str]) -> Result<()> {
        for line in lines {
            writeln!(output, "{line}")?;
        }
        Ok(())
    }

    fn needs_runtime(&self) -> bool {
        // For now, always include runtime - later we can analyze the IR to determine if it's needed
        true
    }

    fn emit_ir(&self, output: &mut String, ir: &ShellIR, indent: usize) -> Result<()> {
        match ir {
            ShellIR::Let { name, value, .. } => {
                self.emit_let_statement(output, name, value, indent)
            }
            ShellIR::Exec { cmd, .. } => self.emit_exec_statement(output, cmd, indent),
            ShellIR::If {
                test,
                then_branch,
                else_branch,
            } => self.emit_if_statement(output, test, then_branch, else_branch.as_deref(), indent),
            ShellIR::Exit { code, message } => {
                self.emit_exit_statement(output, (*code).into(), message.as_ref(), indent)
            }
            ShellIR::Sequence(items) => self.emit_sequence(output, items, indent),
            ShellIR::Noop => self.emit_noop(output, indent),
        }
    }

    fn emit_let_statement(
        &self,
        output: &mut String,
        name: &str,
        value: &ShellValue,
        indent: usize,
    ) -> Result<()> {
        let indent_str = "    ".repeat(indent + 1);
        let var_name = escape_variable_name(name);
        let var_value = self.emit_shell_value(value)?;
        writeln!(output, "{indent_str}readonly {var_name}={var_value}")?;
        Ok(())
    }

    fn emit_exec_statement(&self, output: &mut String, cmd: &Command, indent: usize) -> Result<()> {
        let indent_str = "    ".repeat(indent + 1);
        let command_str = self.emit_command(cmd)?;
        writeln!(output, "{indent_str}{command_str}")?;
        Ok(())
    }

    fn emit_if_statement(
        &self,
        output: &mut String,
        test: &ShellValue,
        then_branch: &ShellIR,
        else_branch: Option<&ShellIR>,
        indent: usize,
    ) -> Result<()> {
        let indent_str = "    ".repeat(indent + 1);
        let test_expr = self.emit_test_expression(test)?;
        writeln!(output, "{indent_str}if {test_expr}; then")?;

        self.emit_ir(output, then_branch, indent + 1)?;

        if let Some(else_ir) = else_branch {
            writeln!(output, "{indent_str}else")?;
            self.emit_ir(output, else_ir, indent + 1)?;
        }

        writeln!(output, "{indent_str}fi")?;
        Ok(())
    }

    fn emit_exit_statement(
        &self,
        output: &mut String,
        code: i32,
        message: Option<&String>,
        indent: usize,
    ) -> Result<()> {
        let indent_str = "    ".repeat(indent + 1);
        if let Some(msg) = message {
            let escaped_msg = escape_shell_string(msg);
            writeln!(output, "{indent_str}echo {escaped_msg} >&2")?;
        }
        writeln!(output, "{indent_str}exit {code}")?;
        Ok(())
    }

    fn emit_sequence(&self, output: &mut String, items: &[ShellIR], indent: usize) -> Result<()> {
        for item in items {
            self.emit_ir(output, item, indent)?;
        }
        Ok(())
    }

    fn emit_noop(&self, output: &mut String, indent: usize) -> Result<()> {
        let indent_str = "    ".repeat(indent + 1);
        writeln!(output, "{indent_str}# noop")?;
        Ok(())
    }

    pub fn emit_shell_value(&self, value: &ShellValue) -> Result<String> {
        match value {
            ShellValue::String(s) => Ok(escape_shell_string(s)),
            ShellValue::Bool(b) => Ok(self.emit_bool_value(*b)),
            ShellValue::Variable(name) => Ok(format!("\"${}\"", escape_variable_name(name))),
            ShellValue::Concat(parts) => self.emit_concatenation(parts),
            ShellValue::CommandSubst(cmd) => {
                let cmd_str = self.emit_command(cmd)?;
                Ok(format!("\"$({cmd_str})\""))
            }
        }
    }

    fn emit_bool_value(&self, value: bool) -> String {
        if value { "true" } else { "false" }.to_string()
    }

    fn emit_concatenation(&self, parts: &[ShellValue]) -> Result<String> {
        let mut result = String::new();
        result.push('"');

        for part in parts {
            self.append_concat_part(&mut result, part)?;
        }

        result.push('"');
        Ok(result)
    }

    fn append_concat_part(&self, result: &mut String, part: &ShellValue) -> Result<()> {
        match part {
            ShellValue::String(s) => result.push_str(s),
            ShellValue::Bool(b) => result.push_str(&self.emit_bool_value(*b)),
            ShellValue::Variable(name) => {
                result.push_str(&format!("${{{}}}", escape_variable_name(name)));
            }
            ShellValue::CommandSubst(cmd) => {
                let cmd_str = self.emit_command(cmd)?;
                result.push_str(&format!("$({cmd_str})"));
            }
            ShellValue::Concat(_) => {
                // Nested concatenation - flatten it
                let nested = self.emit_shell_value(part)?;
                self.append_flattened_content(result, &nested);
            }
        }
        Ok(())
    }

    fn append_flattened_content(&self, result: &mut String, nested: &str) {
        // Remove quotes from nested value and add content
        if nested.starts_with('"') && nested.ends_with('"') {
            result.push_str(&nested[1..nested.len() - 1]);
        } else {
            result.push_str(nested);
        }
    }

    fn emit_command(&self, cmd: &Command) -> Result<String> {
        let mut result = escape_command_name(&cmd.program);

        for arg in &cmd.args {
            result.push(' ');
            result.push_str(&self.emit_shell_value(arg)?);
        }

        Ok(result)
    }

    pub fn emit_test_expression(&self, test: &ShellValue) -> Result<String> {
        match test {
            ShellValue::Bool(true) => Ok("true".to_string()),
            ShellValue::Bool(false) => Ok("false".to_string()),
            ShellValue::Variable(name) => {
                // Test if variable is non-empty
                Ok(format!("test -n \"${}\"", escape_variable_name(name)))
            }
            ShellValue::String(s) => {
                if s == "true" || s == "0" {
                    Ok("true".to_string())
                } else {
                    Ok("false".to_string())
                }
            }
            other => {
                // For complex expressions, evaluate them and test the result
                let value = self.emit_shell_value(other)?;
                Ok(format!("test -n {value}"))
            }
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::ir::{Command, ShellIR, ShellValue};
    use crate::models::Config;

    #[test]
    fn test_emit_simple_let() {
        let config = Config::default();
        let emitter = PosixEmitter::new(config);

        let ir = ShellIR::Let {
            name: "test_var".to_string(),
            value: ShellValue::String("hello world".to_string()),
            effects: Default::default(),
        };

        let result = emitter.emit(&ir).unwrap();
        assert!(result.contains("readonly test_var='hello world'"));
    }

    #[test]
    fn test_emit_command() {
        let config = Config::default();
        let emitter = PosixEmitter::new(config);

        let cmd = Command {
            program: "echo".to_string(),
            args: vec![ShellValue::String("hello".to_string())],
        };

        let ir = ShellIR::Exec {
            cmd,
            effects: Default::default(),
        };

        let result = emitter.emit(&ir).unwrap();
        assert!(result.contains("echo hello"));
    }

    #[test]
    fn test_emit_if_statement() {
        let config = Config::default();
        let emitter = PosixEmitter::new(config);

        let ir = ShellIR::If {
            test: ShellValue::Bool(true),
            then_branch: Box::new(ShellIR::Exec {
                cmd: Command {
                    program: "echo".to_string(),
                    args: vec![ShellValue::String("true branch".to_string())],
                },
                effects: Default::default(),
            }),
            else_branch: None,
        };

        let result = emitter.emit(&ir).unwrap();
        assert!(result.contains("if true; then"));
        assert!(result.contains("echo 'true branch'"));
        assert!(result.contains("fi"));
    }
}
