use anyhow::{bail, Result};

use crate::core::{
    config::{load_config, resolve_project},
    env::{is_inside_tmux, resolve_dir},
    output::{info, success, warn},
    session_name::resolve_session_name,
    state,
    tmux::{
        attach_session, create_session, get_session_paths, has_session, has_tmux, send_keys,
        switch_client,
    },
};

pub fn run(
    session: Option<&str>,
    cwd: Option<&str>,
    name_override: Option<&str>,
    no_attach: bool,
    cmd_flag: Option<&str>,
    force: bool,
) -> Result<()> {
    if !has_tmux() {
        bail!("tmux not found in PATH");
    }

    let config = load_config();

    // --cwd flag: dir-based flow (old positional behavior)
    if cwd.is_some() {
        return run_dir_based(cwd, name_override, no_attach, cmd_flag, force);
    }

    // Session name or config alias provided
    if let Some(target) = session {
        let project = resolve_project(&config, target);

        if let Some(proj) = project {
            // Config alias: resolve the project's directory
            let startup = cmd_flag.or(proj.startup.as_deref());
            let effective_name = name_override.or(Some(target));
            return run_dir_based(
                Some(proj.cwd.as_str()),
                effective_name,
                no_attach,
                startup,
                force,
            );
        }

        // Existing tmux session by name
        if has_session(target) {
            info(&format!("reused: {target}"));
            return do_attach(target, no_attach);
        }

        bail!("session not found: {target}");
    }

    // No args: fall back to current directory
    run_dir_based(None, name_override, no_attach, cmd_flag, force)
}

fn run_dir_based(
    dir_target: Option<&str>,
    name_override: Option<&str>,
    no_attach: bool,
    startup_cmd: Option<&str>,
    force: bool,
) -> Result<()> {
    let dir = resolve_dir(dir_target)?;
    let dir_str = dir.to_string_lossy();
    let session_name = resolve_session_name(&dir_str, name_override);

    if session_name.is_empty() {
        bail!(
            "cannot derive a valid session name from \"{}\"",
            name_override.unwrap_or(&dir_str)
        );
    }

    let existed = has_session(&session_name);

    if !existed {
        if !create_session(&session_name, &dir_str) {
            // Another process may have created it concurrently — reuse if so.
            if !has_session(&session_name) {
                bail!("failed to create session: {session_name}");
            }
            info(&format!("reused: {session_name}"));
        } else {
            success(&format!("created: {session_name}"));
            if let Some(cmd) = startup_cmd {
                if !send_keys(&session_name, cmd) {
                    warn(&format!("startup command may not have been sent: {cmd}"));
                }
            }
        }
    } else {
        let paths = get_session_paths();
        if let Some(existing_path) = paths.get(&session_name) {
            let canon_existing = std::path::Path::new(existing_path)
                .canonicalize()
                .map(|p| p.to_string_lossy().into_owned())
                .unwrap_or_else(|_| existing_path.clone());
            let canon_dir = dir
                .canonicalize()
                .map(|p| p.to_string_lossy().into_owned())
                .unwrap_or_else(|_| dir_str.to_string());
            if canon_existing != canon_dir && !force {
                bail!(
                    "session name collision: {session_name}\n\n  \
                     Existing session path:  {existing_path}\n  \
                     Requested path:         {dir_str}\n\n\
                     Rename the new session:\n  \
                     muxx --cwd {dir_str} --name <new-name>\n\n\
                     Or attach to the existing session anyway:\n  \
                     muxx --cwd {dir_str} --force"
                );
            }
        }
        info(&format!("reused: {session_name}"));
    }

    do_attach(&session_name, no_attach)
}

fn do_attach(session_name: &str, no_attach: bool) -> Result<()> {
    if no_attach {
        return Ok(());
    }

    state::save_last_session(session_name);

    if is_inside_tmux() {
        if !switch_client(session_name) {
            bail!("failed to switch to session: {session_name}");
        }
    } else if !attach_session(session_name) {
        bail!("failed to attach to session: {session_name}");
    }

    Ok(())
}
