trackWork 0.15.0

A terminal-based time tracking application for managing work sessions
use anyhow::Result;
use base64::Engine;

use super::IntegrationOutput;

pub fn log_work(
    jira_url: &str,
    jira_email: &str,
    api_token: &str,
    issue_key: &str,
    time_spent: &str,
    started: &str,
    description: &str,
) -> Result<IntegrationOutput> {
    let url = format!(
        "{}/rest/api/2/issue/{}/worklog",
        jira_url.trim_end_matches('/'),
        issue_key
    );

    let auth = base64::engine::general_purpose::STANDARD
        .encode(format!("{}:{}", jira_email, api_token));

    let mut body = serde_json::json!({
        "timeSpent": time_spent,
        "started": started,
    });
    if !description.is_empty() {
        body["comment"] = serde_json::json!(description);
    }

    let client = reqwest::blocking::Client::new();
    let response = client
        .post(&url)
        .header("Authorization", format!("Basic {}", auth))
        .header("Content-Type", "application/json")
        .json(&body)
        .send();

    match response {
        Ok(resp) => {
            let status = resp.status();
            let body_text = resp.text().unwrap_or_default();
            if status.is_success() {
                // The created worklog object includes its id; capture it so the
                // worklog can be deleted later.
                let worklog_id = serde_json::from_str::<serde_json::Value>(&body_text)
                    .ok()
                    .and_then(|v| v["id"].as_str().map(|s| s.to_string()));
                Ok(IntegrationOutput {
                    success: true,
                    messages: vec![
                        format!("[JIRA] Logged {} to {}", time_spent, issue_key),
                        format!("[JIRA] Response: {}", status),
                    ],
                    worklog_id,
                })
            } else {
                Ok(IntegrationOutput {
                    success: false,
                    messages: vec![
                        format!("[JIRA] Failed to log work: HTTP {}", status),
                        format!("[JIRA] Response: {}", body_text),
                    ],
                    worklog_id: None,
                })
            }
        }
        Err(e) => Ok(IntegrationOutput {
            success: false,
            messages: vec![format!("[JIRA] Request failed: {}", e)],
            worklog_id: None,
        }),
    }
}

/// Delete a previously created worklog from a Jira issue.
pub fn delete_work(
    jira_url: &str,
    jira_email: &str,
    api_token: &str,
    issue_key: &str,
    worklog_id: &str,
) -> Result<IntegrationOutput> {
    let url = format!(
        "{}/rest/api/2/issue/{}/worklog/{}",
        jira_url.trim_end_matches('/'),
        issue_key,
        worklog_id
    );

    let auth = base64::engine::general_purpose::STANDARD
        .encode(format!("{}:{}", jira_email, api_token));

    let client = reqwest::blocking::Client::new();
    let response = client
        .delete(&url)
        .header("Authorization", format!("Basic {}", auth))
        .send();

    match response {
        Ok(resp) => {
            let status = resp.status();
            // Jira returns 204 No Content on success. Treat a 404 (worklog
            // already gone) as success so the local state can still clear.
            if status.is_success() || status.as_u16() == 404 {
                Ok(IntegrationOutput {
                    success: true,
                    messages: vec![format!("[JIRA] Deleted worklog {} from {}", worklog_id, issue_key)],
                    worklog_id: None,
                })
            } else {
                let body_text = resp.text().unwrap_or_default();
                Ok(IntegrationOutput {
                    success: false,
                    messages: vec![
                        format!("[JIRA] Failed to delete worklog: HTTP {}", status),
                        format!("[JIRA] Response: {}", body_text),
                    ],
                    worklog_id: None,
                })
            }
        }
        Err(e) => Ok(IntegrationOutput {
            success: false,
            messages: vec![format!("[JIRA] Request failed: {}", e)],
            worklog_id: None,
        }),
    }
}

pub fn fetch_issue_summary(
    jira_url: &str,
    jira_email: &str,
    api_token: &str,
    issue_key: &str,
) -> Result<String> {
    let url = format!(
        "{}/rest/api/2/issue/{}?fields=summary",
        jira_url.trim_end_matches('/'),
        issue_key
    );

    let auth = base64::engine::general_purpose::STANDARD
        .encode(format!("{}:{}", jira_email, api_token));

    let client = reqwest::blocking::Client::new();
    let response = client
        .get(&url)
        .header("Authorization", format!("Basic {}", auth))
        .header("Content-Type", "application/json")
        .send()?;

    if !response.status().is_success() {
        anyhow::bail!("HTTP {}", response.status());
    }

    let body: serde_json::Value = response.json()?;
    let summary = body["fields"]["summary"]
        .as_str()
        .unwrap_or("")
        .to_string();
    Ok(summary)
}

pub fn open_issue(jira_url: &str, issue_key: &str) -> Result<IntegrationOutput> {
    let url = format!(
        "{}/browse/{}",
        jira_url.trim_end_matches('/'),
        issue_key
    );

    let command = if cfg!(target_os = "linux") {
        format!("xdg-open '{}'", url)
    } else if cfg!(target_os = "macos") {
        format!("open '{}'", url)
    } else if cfg!(target_os = "windows") {
        format!("start '{}'", url)
    } else {
        format!("xdg-open '{}'", url)
    };

    let mut process = if cfg!(target_os = "windows") {
        let mut cmd = std::process::Command::new("cmd");
        cmd.args(&["/C", &command]);
        cmd
    } else {
        let mut cmd = std::process::Command::new("sh");
        cmd.args(&["-c", &command]);
        cmd
    };

    process.stdin(std::process::Stdio::null());
    process.stdout(std::process::Stdio::piped());
    process.stderr(std::process::Stdio::piped());

    match process.output() {
        Ok(output) => {
            if output.status.success() {
                Ok(IntegrationOutput {
                    success: true,
                    messages: vec![format!("[JIRA] Opened {}", url)],
                    worklog_id: None,
                })
            } else {
                let stderr = String::from_utf8_lossy(&output.stderr).to_string();
                Ok(IntegrationOutput {
                    success: false,
                    messages: vec![
                        format!("[JIRA] Failed to open browser: {}", output.status),
                        format!("[JIRA] stderr: {}", stderr),
                    ],
                    worklog_id: None,
                })
            }
        }
        Err(e) => Ok(IntegrationOutput {
            success: false,
            messages: vec![format!("[JIRA] Failed to execute open command: {}", e)],
            worklog_id: None,
        }),
    }
}