use crate::DriverCallback;
use crate::DriverContext;
use crate::{
    DriverCategory,
    types::{Driver, DriverParameter},
};
use crate::{ensure_dir, file_exists, read_file_content, validate_path, write_file_content};
use anyhow::Result;
use serde_json::{Value, json};
use std::collections::HashMap;

#[derive(Debug)]
pub struct TextReadDriver;

#[async_trait::async_trait]
impl Driver for TextReadDriver {
    fn name(&self) -> &str {
        "text_read"
    }

    fn description(&self) -> &str {
        "Read plain text file content"
    }

    fn usage_hint(&self) -> &str {
        "Use this skill when the user wants to read a plain text file, view logs, or extract content from .txt files"
    }

    fn parameters(&self) -> Vec<DriverParameter> {
        vec![
            DriverParameter {
                name: "path".to_string(),
                param_type: "string".to_string(),
                description: "Path to the text file".to_string(),
                required: true,
                default: None,
                example: Some(Value::String("notes.txt".to_string())),
                enum_values: None,
            },
            DriverParameter {
                name: "encoding".to_string(),
                param_type: "string".to_string(),
                description: "File encoding (utf-8, utf-16, latin1)".to_string(),
                required: false,
                default: Some(Value::String("utf-8".to_string())),
                example: Some(Value::String("utf-8".to_string())),
                enum_values: Some(vec![
                    "utf-8".to_string(),
                    "utf-16".to_string(),
                    "latin1".to_string(),
                ]),
            },
            DriverParameter {
                name: "limit_lines".to_string(),
                param_type: "integer".to_string(),
                description: "Maximum number of lines to read".to_string(),
                required: false,
                default: None,
                example: Some(Value::Number(100.into())),
                enum_values: None,
            },
            DriverParameter {
                name: "start_line".to_string(),
                param_type: "integer".to_string(),
                description: "Line number to start reading from (0-indexed)".to_string(),
                required: false,
                default: Some(Value::Number(0.into())),
                example: Some(Value::Number(10.into())),
                enum_values: None,
            },
        ]
    }

    fn example_call(&self) -> Value {
        json!({
            "action": "text_read",
            "parameters": {
                "path": "notes.txt"
            }
        })
    }

    fn example_output(&self) -> String {
        "Line 1: Hello world\nLine 2: This is a text file".to_string()
    }

    fn category(&self) -> DriverCategory {
        DriverCategory::Document
    }

    async fn execute(
        &self,
        parameters: &HashMap<String, Value>,
        callback: Option<&dyn DriverCallback>,
        context: Option<&DriverContext>,
    ) -> Result<String> {
        let path = parameters
            .get("path")
            .and_then(|v| v.as_str())
            .ok_or_else(|| anyhow::anyhow!("Missing 'path' parameter"))?;
        let encoding = parameters
            .get("encoding")
            .and_then(|v| v.as_str())
            .unwrap_or("utf-8");
        let limit_lines = parameters
            .get("limit_lines")
            .and_then(|v| v.as_u64())
            .map(|v| v as usize);
        let start_line = parameters
            .get("start_line")
            .and_then(|v| v.as_u64())
            .unwrap_or(0) as usize;

        let validated_path = validate_path(path, None)?;
        if !file_exists(&validated_path.to_string_lossy()) {
            anyhow::bail!("Text file not found: {}", path);
        }

        let content = read_file_content_with_encoding(&validated_path.to_string_lossy(), encoding)?;
        let lines: Vec<&str> = content.lines().collect();

        if start_line >= lines.len() {
            anyhow::bail!(
                "Start line {} exceeds total lines ({})",
                start_line,
                lines.len()
            );
        }

        let end_line = if let Some(limit) = limit_lines {
            (start_line + limit).min(lines.len())
        } else {
            lines.len()
        };

        let selected_lines = &lines[start_line..end_line];
        let mut output = String::new();

        for (i, line) in selected_lines.iter().enumerate() {
            output.push_str(&format!("Line {}: {}\n", start_line + i + 1, line));
        }

        if limit_lines.is_some() && end_line < lines.len() {
            output.push_str(&format!("... and {} more lines\n", lines.len() - end_line));
        }

        output.push_str(&format!("Total lines: {}", lines.len()));

        Ok(output)
    }

    fn validate(&self, parameters: &HashMap<String, Value>) -> Result<()> {
        parameters
            .get("path")
            .and_then(|v| v.as_str())
            .ok_or_else(|| anyhow::anyhow!("Missing required parameter: path"))?;
        Ok(())
    }
}

#[derive(Debug)]
pub struct TextWriteDriver;

#[async_trait::async_trait]
impl Driver for TextWriteDriver {
    fn name(&self) -> &str {
        "text_write"
    }

    fn description(&self) -> &str {
        "Write text content to a file"
    }

    fn usage_hint(&self) -> &str {
        "Use this skill when the user wants to create, save, or append to a plain text file"
    }

    fn parameters(&self) -> Vec<DriverParameter> {
        vec![
            DriverParameter {
                name: "path".to_string(),
                param_type: "string".to_string(),
                description: "Path to save the text file".to_string(),
                required: true,
                default: None,
                example: Some(Value::String("output.txt".to_string())),
                enum_values: None,
            },
            DriverParameter {
                name: "content".to_string(),
                param_type: "string".to_string(),
                description: "Text content to write".to_string(),
                required: true,
                default: None,
                example: Some(Value::String("Hello, World!".to_string())),
                enum_values: None,
            },
            DriverParameter {
                name: "append".to_string(),
                param_type: "boolean".to_string(),
                description: "Append to existing file instead of overwriting".to_string(),
                required: false,
                default: Some(Value::Bool(false)),
                example: Some(Value::Bool(true)),
                enum_values: None,
            },
            DriverParameter {
                name: "encoding".to_string(),
                param_type: "string".to_string(),
                description: "File encoding (utf-8, utf-16)".to_string(),
                required: false,
                default: Some(Value::String("utf-8".to_string())),
                example: Some(Value::String("utf-8".to_string())),
                enum_values: Some(vec!["utf-8".to_string(), "utf-16".to_string()]),
            },
        ]
    }

    fn example_call(&self) -> Value {
        json!({
            "action": "text_write",
            "parameters": {
                "path": "output.txt",
                "content": "Hello, World!"
            }
        })
    }

    fn example_output(&self) -> String {
        "Text written to: output.txt".to_string()
    }

    fn category(&self) -> DriverCategory {
        DriverCategory::Document
    }

    async fn execute(
        &self,
        parameters: &HashMap<String, Value>,
        callback: Option<&dyn DriverCallback>,
        context: Option<&DriverContext>,
    ) -> Result<String> {
        let path = parameters
            .get("path")
            .and_then(|v| v.as_str())
            .ok_or_else(|| anyhow::anyhow!("Missing 'path' parameter"))?;
        let content = parameters
            .get("content")
            .and_then(|v| v.as_str())
            .ok_or_else(|| anyhow::anyhow!("Missing 'content' parameter"))?;
        let append = parameters
            .get("append")
            .and_then(|v| v.as_bool())
            .unwrap_or(false);
        let encoding = parameters
            .get("encoding")
            .and_then(|v| v.as_str())
            .unwrap_or("utf-8");

        let validated_path = validate_path(path, None)?;
        if let Some(parent) = validated_path.parent() {
            ensure_dir(&parent.to_string_lossy())?;
        }

        if append {
            write_file_content_with_encoding(
                &validated_path.to_string_lossy(),
                content,
                true,
                encoding,
            )?;
            Ok(format!("Content appended to text file: {}", path))
        } else {
            write_file_content_with_encoding(
                &validated_path.to_string_lossy(),
                content,
                false,
                encoding,
            )?;
            Ok(format!("Text written to: {}", path))
        }
    }

    fn validate(&self, parameters: &HashMap<String, Value>) -> Result<()> {
        parameters
            .get("path")
            .and_then(|v| v.as_str())
            .ok_or_else(|| anyhow::anyhow!("Missing required parameter: path"))?;
        parameters
            .get("content")
            .and_then(|v| v.as_str())
            .ok_or_else(|| anyhow::anyhow!("Missing required parameter: content"))?;
        Ok(())
    }
}

#[derive(Debug)]
pub struct TextSearchDriver;

#[async_trait::async_trait]
impl Driver for TextSearchDriver {
    fn name(&self) -> &str {
        "text_search"
    }

    fn description(&self) -> &str {
        "Search for patterns in text files"
    }

    fn usage_hint(&self) -> &str {
        "Use this skill when the user wants to search for text patterns, find lines containing specific words, or grep through files"
    }

    fn parameters(&self) -> Vec<DriverParameter> {
        vec![
            DriverParameter {
                name: "path".to_string(),
                param_type: "string".to_string(),
                description: "Path to the text file to search".to_string(),
                required: true,
                default: None,
                example: Some(Value::String("log.txt".to_string())),
                enum_values: None,
            },
            DriverParameter {
                name: "pattern".to_string(),
                param_type: "string".to_string(),
                description: "Search pattern (supports regex)".to_string(),
                required: true,
                default: None,
                example: Some(Value::String("error".to_string())),
                enum_values: None,
            },
            DriverParameter {
                name: "case_sensitive".to_string(),
                param_type: "boolean".to_string(),
                description: "Case sensitive search".to_string(),
                required: false,
                default: Some(Value::Bool(false)),
                example: Some(Value::Bool(true)),
                enum_values: None,
            },
            DriverParameter {
                name: "regex".to_string(),
                param_type: "boolean".to_string(),
                description: "Treat pattern as regular expression".to_string(),
                required: false,
                default: Some(Value::Bool(false)),
                example: Some(Value::Bool(true)),
                enum_values: None,
            },
        ]
    }

    fn example_call(&self) -> Value {
        json!({
            "action": "text_search",
            "parameters": {
                "path": "log.txt",
                "pattern": "error"
            }
        })
    }

    fn example_output(&self) -> String {
        "Line 5: Error: connection failed\nLine 12: error: invalid input".to_string()
    }

    fn category(&self) -> DriverCategory {
        DriverCategory::Document
    }

    async fn execute(
        &self,
        parameters: &HashMap<String, Value>,
        callback: Option<&dyn DriverCallback>,
        context: Option<&DriverContext>,
    ) -> Result<String> {
        let path = parameters
            .get("path")
            .and_then(|v| v.as_str())
            .ok_or_else(|| anyhow::anyhow!("Missing 'path' parameter"))?;
        let pattern = parameters
            .get("pattern")
            .and_then(|v| v.as_str())
            .ok_or_else(|| anyhow::anyhow!("Missing 'pattern' parameter"))?;
        let case_sensitive = parameters
            .get("case_sensitive")
            .and_then(|v| v.as_bool())
            .unwrap_or(false);
        let use_regex = parameters
            .get("regex")
            .and_then(|v| v.as_bool())
            .unwrap_or(false);
        let validated_path = validate_path(path, None)?;
        if !file_exists(&validated_path.to_string_lossy()) {
            anyhow::bail!("Text file not found: {}", path);
        }
        let content = read_file_content(&validated_path.to_string_lossy())?;
        let lines: Vec<&str> = content.lines().collect();
        let mut matches = Vec::new();
        if use_regex {
            let regex_pattern = if case_sensitive {
                regex::Regex::new(pattern)?
            } else {
                regex::Regex::new(&format!("(?i){}", pattern))?
            };

            for (i, line) in lines.iter().enumerate() {
                if regex_pattern.is_match(line) {
                    matches.push((i + 1, *line));
                }
            }
        } else {
            let search_pattern = if case_sensitive {
                pattern.to_string()
            } else {
                pattern.to_lowercase()
            };
            for (i, line) in lines.iter().enumerate() {
                let check_line = if case_sensitive {
                    line.to_string()
                } else {
                    line.to_lowercase()
                };
                if check_line.contains(&search_pattern) {
                    matches.push((i + 1, *line));
                }
            }
        }
        if matches.is_empty() {
            Ok(format!("No matches found for pattern: {}", pattern))
        } else {
            let mut output = String::new();
            let matches_len = matches.len();
            for (line_num, line) in matches {
                output.push_str(&format!("Line {}: {}\n", line_num, line));
            }
            output.push_str(&format!("\nTotal matches: {}", matches_len));
            Ok(output)
        }
    }

    fn validate(&self, parameters: &HashMap<String, Value>) -> Result<()> {
        parameters
            .get("path")
            .and_then(|v| v.as_str())
            .ok_or_else(|| anyhow::anyhow!("Missing required parameter: path"))?;
        parameters
            .get("pattern")
            .and_then(|v| v.as_str())
            .ok_or_else(|| anyhow::anyhow!("Missing required parameter: pattern"))?;
        Ok(())
    }
}

fn read_file_content_with_encoding(path: &str, encoding: &str) -> Result<String> {
    use std::fs;

    let bytes = fs::read(path)?;

    match encoding {
        "utf-8" => Ok(String::from_utf8(bytes)?),
        "utf-16" => {
            let utf16_data: Vec<u16> = bytes
                .chunks_exact(2)
                .map(|c| u16::from_le_bytes([c[0], c[1]]))
                .collect();
            String::from_utf16(&utf16_data)
                .map_err(|e| anyhow::anyhow!("UTF-16 decode error: {}", e))
        }
        "latin1" => Ok(bytes.iter().map(|&b| b as char).collect()),
        _ => anyhow::bail!("Unsupported encoding: {}", encoding),
    }
}

fn write_file_content_with_encoding(
    path: &str,
    content: &str,
    append: bool,
    encoding: &str,
) -> Result<()> {
    use std::fs::{File, OpenOptions};
    use std::io::Write;

    let bytes: Vec<u8> = match encoding {
        "utf-8" => content.as_bytes().to_vec(),
        "utf-16" => {
            let mut utf16 = Vec::new();
            for c in content.encode_utf16() {
                utf16.extend_from_slice(&c.to_le_bytes());
            }
            utf16
        }
        _ => anyhow::bail!("Unsupported encoding for write: {}", encoding),
    };

    let mut file = if append {
        OpenOptions::new().create(true).append(true).open(path)?
    } else {
        File::create(path)?
    };

    file.write_all(&bytes)?;
    Ok(())
}
