use anyhow::{Context, Result};
use serde::{Deserialize, Serialize};
use std::fs;
use std::path::Path;
use std::process::Command;

#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct Window {
    pub name: String,
    #[serde(default)]
    pub panes: Vec<String>,
    #[serde(default)]
    pub layout: Option<String>,
}

#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct SessionConfig {
    #[serde(default)]
    pub windows: Vec<Window>,
}

impl SessionConfig {
    pub fn load_from_project(project_path: &Path) -> Result<Option<Self>> {
        let config_path = project_path.join(".tmuxido.toml");

        if !config_path.exists() {
            return Ok(None);
        }

        let content = fs::read_to_string(&config_path)
            .with_context(|| format!("Failed to read session config: {}", config_path.display()))?;

        let config: SessionConfig = toml::from_str(&content).with_context(|| {
            format!("Failed to parse session config: {}", config_path.display())
        })?;

        Ok(Some(config))
    }
}

pub struct TmuxSession {
    pub(crate) session_name: String,
    project_path: String,
    base_index: usize,
}

impl TmuxSession {
    pub fn new(project_path: &Path) -> Self {
        let session_name = project_path
            .file_name()
            .and_then(|n| n.to_str())
            .unwrap_or("project")
            .replace('.', "_")
            .replace(' ', "-");

        let base_index = Self::get_base_index();

        Self {
            session_name,
            project_path: project_path.display().to_string(),
            base_index,
        }
    }

    fn get_base_index() -> usize {
        // Try to get base-index from tmux
        let output = Command::new("tmux")
            .args(["show-options", "-gv", "base-index"])
            .output();

        if let Ok(output) = output
            && output.status.success()
        {
            let index_str = String::from_utf8_lossy(&output.stdout);
            if let Ok(index) = index_str.trim().parse::<usize>() {
                return index;
            }
        }

        // Default to 0 if we can't determine
        0
    }

    pub fn create(&self, config: &SessionConfig) -> Result<()> {
        // Check if we're already inside a tmux session
        let inside_tmux = std::env::var("TMUX").is_ok();

        // Check if session already exists
        let session_exists = Command::new("tmux")
            .args(["has-session", "-t", &self.session_name])
            .output()
            .map(|o| o.status.success())
            .unwrap_or(false);

        if session_exists {
            // Session exists, just switch to it
            if inside_tmux {
                Command::new("tmux")
                    .args(["switch-client", "-t", &self.session_name])
                    .status()
                    .context("Failed to switch to existing session")?;
            } else {
                Command::new("tmux")
                    .args(["attach-session", "-t", &self.session_name])
                    .status()
                    .context("Failed to attach to existing session")?;
            }
            return Ok(());
        }

        // Create new session
        if config.windows.is_empty() {
            // Create simple session with one window
            self.create_simple_session()?;
        } else {
            // Create session with custom windows
            self.create_custom_session(config)?;
        }

        // Attach or switch to the session
        if inside_tmux {
            Command::new("tmux")
                .args(["switch-client", "-t", &self.session_name])
                .status()
                .context("Failed to switch to new session")?;
        } else {
            Command::new("tmux")
                .args(["attach-session", "-t", &self.session_name])
                .status()
                .context("Failed to attach to new session")?;
        }

        Ok(())
    }

    fn create_simple_session(&self) -> Result<()> {
        // Create a detached session with one window
        Command::new("tmux")
            .args([
                "new-session",
                "-d",
                "-s",
                &self.session_name,
                "-c",
                &self.project_path,
            ])
            .status()
            .context("Failed to create tmux session")?;

        Ok(())
    }

    fn create_custom_session(&self, config: &SessionConfig) -> Result<()> {
        // Create session with first window
        let first_window = &config.windows[0];
        Command::new("tmux")
            .args([
                "new-session",
                "-d",
                "-s",
                &self.session_name,
                "-n",
                &first_window.name,
                "-c",
                &self.project_path,
            ])
            .status()
            .context("Failed to create tmux session")?;

        // Create panes for first window if specified
        if !first_window.panes.is_empty() {
            self.create_panes(self.base_index, &first_window.panes)?;
        }

        // Apply layout for first window if specified
        if let Some(layout) = &first_window.layout {
            self.apply_layout(self.base_index, layout)?;
        }

        // Create additional windows
        for (index, window) in config.windows.iter().skip(1).enumerate() {
            let window_index = self.base_index + index + 1;

            Command::new("tmux")
                .args([
                    "new-window",
                    "-t",
                    &format!("{}:{}", self.session_name, window_index),
                    "-n",
                    &window.name,
                    "-c",
                    &self.project_path,
                ])
                .status()
                .with_context(|| format!("Failed to create window: {}", window.name))?;

            // Create panes if specified
            if !window.panes.is_empty() {
                self.create_panes(window_index, &window.panes)?;
            }

            // Apply layout if specified
            if let Some(layout) = &window.layout {
                self.apply_layout(window_index, layout)?;
            }
        }

        // Select the first window
        Command::new("tmux")
            .args([
                "select-window",
                "-t",
                &format!("{}:{}", self.session_name, self.base_index),
            ])
            .status()
            .context("Failed to select first window")?;

        Ok(())
    }

    fn create_panes(&self, window_index: usize, panes: &[String]) -> Result<()> {
        for (pane_index, command) in panes.iter().enumerate() {
            let target = format!("{}:{}", self.session_name, window_index);

            // First pane already exists (created with the window), skip split
            if pane_index > 0 {
                // Create new pane by splitting
                Command::new("tmux")
                    .args(["split-window", "-t", &target, "-c", &self.project_path])
                    .status()
                    .context("Failed to split pane")?;
            }

            // Send the command to the pane if it's not empty
            if !command.is_empty() {
                let pane_target = format!("{}:{}.{}", self.session_name, window_index, pane_index);
                Command::new("tmux")
                    .args(["send-keys", "-t", &pane_target, command, "Enter"])
                    .status()
                    .context("Failed to send keys to pane")?;
            }
        }

        Ok(())
    }

    fn apply_layout(&self, window_index: usize, layout: &str) -> Result<()> {
        Command::new("tmux")
            .args([
                "select-layout",
                "-t",
                &format!("{}:{}", self.session_name, window_index),
                layout,
            ])
            .status()
            .with_context(|| format!("Failed to apply layout: {}", layout))?;

        Ok(())
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use std::path::Path;

    #[test]
    fn should_replace_dots_with_underscores_in_session_name() {
        let session = TmuxSession::new(Path::new("/home/user/my.project"));
        assert_eq!(session.session_name, "my_project");
    }

    #[test]
    fn should_replace_spaces_with_dashes_in_session_name() {
        let session = TmuxSession::new(Path::new("/home/user/my project"));
        assert_eq!(session.session_name, "my-project");
    }

    #[test]
    fn should_use_project_fallback_when_path_has_no_filename() {
        let session = TmuxSession::new(Path::new("/"));
        assert_eq!(session.session_name, "project");
    }

    #[test]
    fn should_parse_window_from_toml() {
        let toml_str = r#"
            [[windows]]
            name = "editor"
            panes = ["nvim ."]
        "#;
        let config: SessionConfig = toml::from_str(toml_str).unwrap();
        assert_eq!(config.windows[0].name, "editor");
        assert_eq!(config.windows[0].panes, vec!["nvim ."]);
    }

    #[test]
    fn should_parse_session_config_with_layout() {
        let toml_str = r#"
            [[windows]]
            name = "main"
            layout = "tiled"
            panes = ["vim", "bash"]
        "#;
        let config: SessionConfig = toml::from_str(toml_str).unwrap();
        assert_eq!(config.windows[0].layout, Some("tiled".to_string()));
        assert_eq!(config.windows[0].panes.len(), 2);
    }
}
