use std::process::Command;

use anyhow::{Context, Result};
use chrono::{DateTime, Local, Utc};

use fyi_core::{Event, EventQuery, EventType, Order, Storage, format_timestamp};

/// Default limit for the show command
const DEFAULT_SHOW_LIMIT: usize = 20;

/// Options for the `show` command.
///
/// Using a struct instead of individual parameters makes the API more maintainable
/// and prevents parameter order mistakes, especially with multiple `Option<String>` fields.
#[derive(Debug, Clone)]
pub struct ShowOptions {
    /// Maximum number of events to display (default: 20)
    pub limit: usize,
    /// Filter by session ID
    pub session: Option<String>,
    /// Filter by event type
    pub event_type: Option<String>,
    /// Filter by permission mode
    pub permission_mode: Option<String>,
    /// Filter by framework (claude, gemini, cursor, etc.)
    pub framework: Option<String>,
    /// Output as JSON instead of table format
    pub json: bool,
}

impl Default for ShowOptions {
    fn default() -> Self {
        Self {
            limit: DEFAULT_SHOW_LIMIT,
            session: None,
            event_type: None,
            permission_mode: None,
            framework: None,
            json: false,
        }
    }
}

impl ShowOptions {
    /// Create new options with default values.
    pub fn new() -> Self {
        Self::default()
    }

    /// Set the limit (maximum events to display).
    pub fn limit(mut self, limit: usize) -> Self {
        self.limit = limit;
        self
    }

    /// Set the session filter.
    pub fn session(mut self, session: impl Into<Option<String>>) -> Self {
        self.session = session.into();
        self
    }

    /// Set the event type filter.
    pub fn event_type(mut self, event_type: impl Into<Option<String>>) -> Self {
        self.event_type = event_type.into();
        self
    }

    /// Set the permission mode filter.
    pub fn permission_mode(mut self, permission_mode: impl Into<Option<String>>) -> Self {
        self.permission_mode = permission_mode.into();
        self
    }

    /// Set the framework filter.
    pub fn framework(mut self, framework: impl Into<Option<String>>) -> Self {
        self.framework = framework.into();
        self
    }

    /// Set JSON output mode.
    pub fn json(mut self, json: bool) -> Self {
        self.json = json;
        self
    }
}

// Column widths for table formatting
const COL_TIME: usize = 19;
const COL_APP: usize = 3;
const COL_EVENT: usize = 18;
const COL_SESSION: usize = 12;
const COL_PID: usize = 7;
const COL_DETAILS_MIN: usize = 10;
const COL_DETAILS_DEFAULT: usize = 40;
const DEFAULT_TERMINAL_WIDTH: usize = 120;

/// Get terminal width using stty, falling back to default
fn get_terminal_width() -> usize {
    Command::new("stty")
        .args(["size"])
        .stdin(std::process::Stdio::inherit())
        .output()
        .ok()
        .and_then(|output| {
            let stdout = String::from_utf8_lossy(&output.stdout);
            // stty size returns "rows cols"
            stdout.split_whitespace().nth(1)?.parse().ok()
        })
        .unwrap_or(DEFAULT_TERMINAL_WIDTH)
}

/// Calculate the details column width based on terminal width and whether APP column is shown
pub fn calculate_details_width(show_app: bool) -> usize {
    let term_width = get_terminal_width();
    let fixed_width = if show_app {
        COL_TIME + 2 + COL_APP + 2 + COL_EVENT + 2 + COL_SESSION + 2 + COL_PID + 2
    } else {
        COL_TIME + 2 + COL_EVENT + 2 + COL_SESSION + 2 + COL_PID + 2
    };
    let available = term_width.saturating_sub(fixed_width);
    available.clamp(COL_DETAILS_MIN, COL_DETAILS_DEFAULT)
}

pub fn run_show<S: Storage>(storage: &S, options: ShowOptions) -> Result<()> {
    // Query all events (now includes API requests with event_type = ApiRequest)
    let query = EventQuery::new()
        .with_session(options.session)
        .with_event_type(options.event_type)
        .with_permission_mode(options.permission_mode)
        .with_framework(options.framework)
        .with_direction(Order::Desc)
        .with_limit(options.limit);

    let mut events = storage.query(&query).context("failed to query events")?;

    // Reverse to get oldest first for display
    events.reverse();

    if options.json {
        print_json_events(&events)?;
    } else {
        print_table_events(&events);
    }

    Ok(())
}

fn print_json_events(events: &[Event]) -> Result<()> {
    #[derive(serde::Serialize)]
    struct JsonEvent {
        id: Option<i64>,
        machine_id: String,
        #[serde(with = "fyi_core::timestamp_millis")]
        timestamp: DateTime<Utc>,
        time: String,
        event_type: String,
        session_id: String,
        framework: Option<String>,
        tool_use_id: Option<String>,
        spawned_agent_id: Option<String>,
        tool_name: Option<String>,
        subagent_type: Option<String>,
        pid: Option<i32>,
        cwd: Option<String>,
        permission_mode: Option<String>,
        transcript_path: Option<String>,
        // API request fields
        model: Option<String>,
        tokens_input: Option<i64>,
        tokens_output: Option<i64>,
        tokens_cache_read: Option<i64>,
        tokens_cache_creation: Option<i64>,
        cost_usd: Option<f64>,
        duration_ms: Option<i64>,
        payload: Option<serde_json::Value>,
        metadata: Option<serde_json::Value>,
    }

    let json_events: Vec<JsonEvent> = events
        .iter()
        .map(|e| {
            let time = format_timestamp(e.timestamp);
            let payload: Option<serde_json::Value> = e
                .payload
                .as_ref()
                .and_then(|p| serde_json::from_str(p).ok());
            let metadata: Option<serde_json::Value> = e
                .metadata
                .as_ref()
                .and_then(|m| serde_json::from_str(m).ok());

            JsonEvent {
                id: e.id,
                machine_id: e.machine_id.clone(),
                timestamp: e.timestamp,
                time,
                event_type: get_display_event_type(e),
                session_id: e.session_id.clone(),
                framework: e.framework.clone(),
                tool_use_id: e.tool_use_id.clone(),
                spawned_agent_id: e.spawned_agent_id.clone(),
                tool_name: e.tool_name.clone(),
                subagent_type: e.subagent_type.clone(),
                pid: e.pid,
                cwd: e.cwd.clone(),
                permission_mode: e.permission_mode.clone(),
                transcript_path: e.transcript_path.clone(),
                model: e.model.clone(),
                tokens_input: e.tokens_input,
                tokens_output: e.tokens_output,
                tokens_cache_read: e.tokens_cache_read,
                tokens_cache_creation: e.tokens_cache_creation,
                cost_usd: e.cost_usd,
                duration_ms: e.duration_ms,
                payload,
                metadata,
            }
        })
        .collect();

    println!(
        "{}",
        serde_json::to_string_pretty(&json_events).context("failed to serialize events")?
    );
    Ok(())
}

fn print_table_events(events: &[Event]) {
    if events.is_empty() {
        println!("No events found.");
        return;
    }

    let show_app = true; // Always show APP column
    let details_width = calculate_details_width(show_app);
    print_table_header(show_app, details_width);

    for event in events {
        print_event_row(event, show_app, details_width, false);
    }

    // Count events and API requests separately
    let api_count = events
        .iter()
        .filter(|e| e.event_type == EventType::ApiRequest)
        .count();
    let event_count = events.len() - api_count;

    if api_count > 0 {
        println!("\n{} event(s), {} API request(s)", event_count, api_count);
    } else {
        println!("\n{} event(s)", event_count);
    }
}

/// Get 3-letter abbreviation for a framework
pub fn framework_abbrev(framework: Option<&str>) -> &'static str {
    match framework {
        Some("claude") => "CLA",
        Some("gemini") => "GEM",
        Some("cursor") => "CUR",
        Some("opencode") => "OPE",
        Some("codex") => "COD",
        Some(_) => "???",
        None => "---",
    }
}

/// Print the table header
pub fn print_table_header(show_app: bool, details_width: usize) {
    if show_app {
        println!(
            "{:<COL_TIME$}  {:<COL_APP$}  {:<COL_EVENT$}  {:<COL_SESSION$}  {:<COL_PID$}  DETAILS",
            "TIME", "APP", "EVENT", "SESSION", "PID"
        );
        let total_width = COL_TIME
            + 2
            + COL_APP
            + 2
            + COL_EVENT
            + 2
            + COL_SESSION
            + 2
            + COL_PID
            + 2
            + details_width;
        println!("{}", "-".repeat(total_width));
    } else {
        println!(
            "{:<COL_TIME$}  {:<COL_EVENT$}  {:<COL_SESSION$}  {:<COL_PID$}  DETAILS",
            "TIME", "EVENT", "SESSION", "PID"
        );
        let total_width =
            COL_TIME + 2 + COL_EVENT + 2 + COL_SESSION + 2 + COL_PID + 2 + details_width;
        println!("{}", "-".repeat(total_width));
    }
}

/// Truncate a string to a maximum number of characters, returning a reference to the truncated slice.
///
/// This function is UTF-8 safe - it truncates at character boundaries, not byte boundaries.
/// If the string has fewer characters than `max_chars`, returns the entire string.
fn truncate_str(s: &str, max_chars: usize) -> &str {
    match s.char_indices().nth(max_chars) {
        Some((idx, _)) => &s[..idx],
        None => s,
    }
}

/// Print a single event as a table row
///
/// When `raw_mode` is true, uses `\r\n` line endings (for raw terminal mode).
/// When `raw_mode` is false, uses normal `\n` line endings.
pub fn print_event_row(event: &Event, show_app: bool, details_width: usize, raw_mode: bool) {
    print_event_row_impl(event, show_app, raw_mode, details_width);
}

/// Get the display event type name for an event
/// For API requests, check metadata for Codex activity event types
fn get_display_event_type(event: &Event) -> String {
    if event.event_type == EventType::ApiRequest {
        // For 0-token entries, check metadata for event_type (Codex activity events)
        if event.tokens_input == Some(0)
            && event.tokens_output == Some(0)
            && let Some(ref metadata) = event.metadata
            && let Ok(json) = serde_json::from_str::<serde_json::Value>(metadata)
            && let Some(event_type) = json.get("event_type").and_then(|v| v.as_str())
        {
            return match event_type {
                "conversation_starts" => "ConvStart".to_string(),
                "user_prompt" => "UserPrompt".to_string(),
                "tool_decision" => "ToolDecision".to_string(),
                "tool_result" => "ToolResult".to_string(),
                "api_request" => "ApiMeta".to_string(),
                _ => "ApiRequest".to_string(),
            };
        }
        "ApiRequest".to_string()
    } else {
        event.event_type.to_string()
    }
}

/// Internal implementation for printing event rows
fn print_event_row_impl(event: &Event, show_app: bool, raw_mode: bool, details_width: usize) {
    let local_time: DateTime<Local> = event.timestamp.into();
    let time_str = local_time.format("%Y-%m-%d %H:%M:%S").to_string();
    let time = truncate_str(&time_str, COL_TIME);
    let event_type_str = get_display_event_type(event);
    let event_type = truncate_str(&event_type_str, COL_EVENT);
    let session_short = truncate_str(&event.session_id, COL_SESSION);
    let pid_str = event.pid.map_or_else(|| "-".to_string(), |p| p.to_string());
    let details_str = format_details(event);
    let details = truncate_str(&details_str, details_width);

    if show_app {
        let app = framework_abbrev(event.framework.as_deref());
        if raw_mode {
            print!(
                "{:<COL_TIME$}  {:<COL_APP$}  {:<COL_EVENT$}  {:<COL_SESSION$}  {:<COL_PID$}  {}\r\n",
                time, app, event_type, session_short, pid_str, details
            );
        } else {
            println!(
                "{:<COL_TIME$}  {:<COL_APP$}  {:<COL_EVENT$}  {:<COL_SESSION$}  {:<COL_PID$}  {}",
                time, app, event_type, session_short, pid_str, details
            );
        }
    } else if raw_mode {
        print!(
            "{:<COL_TIME$}  {:<COL_EVENT$}  {:<COL_SESSION$}  {:<COL_PID$}  {}\r\n",
            time, event_type, session_short, pid_str, details
        );
    } else {
        println!(
            "{:<COL_TIME$}  {:<COL_EVENT$}  {:<COL_SESSION$}  {:<COL_PID$}  {}",
            time, event_type, session_short, pid_str, details
        );
    }
}

/// Truncate a string with ellipsis prefix if it exceeds max_len.
/// Returns the last (max_len - 3) characters prefixed with "...".
///
/// # Panics
/// Panics if `max_len < 4` (need room for "..." plus at least one char).
fn truncate_with_prefix(s: &str, max_len: usize) -> String {
    assert!(
        max_len >= 4,
        "max_len must be at least 4 to accommodate ellipsis"
    );
    if s.len() > max_len {
        format!("...{}", &s[s.len() - (max_len - 3)..])
    } else {
        s.to_string()
    }
}

/// Truncate a string from the start if it exceeds max_len.
/// Returns the first max_len characters (no ellipsis added).
fn truncate_from_start(s: &str, max_len: usize) -> &str {
    if s.len() > max_len { &s[..max_len] } else { s }
}

/// Parse JSON payload from an event, returning None if absent or invalid.
fn parse_payload(event: &Event) -> Option<serde_json::Value> {
    event
        .payload
        .as_ref()
        .and_then(|p| serde_json::from_str(p).ok())
}

/// Format details for PreToolUse and PostToolUse events.
fn format_tool_use_details(event: &Event, parts: &mut Vec<String>) {
    if let Some(ref tool) = event.tool_name {
        parts.push(format!("tool={}", tool));
    }

    if let Some(ref subagent) = event.subagent_type {
        parts.push(format!("subagent={}", subagent));
    }

    if let Some(ref agent_id) = event.spawned_agent_id {
        let short_id = truncate_from_start(agent_id, 12);
        parts.push(format!("spawned={}", short_id));
    }
}

/// Format details for Notification events.
fn format_notification_details(event: &Event, parts: &mut Vec<String>) {
    let Some(payload) = parse_payload(event) else {
        return;
    };

    let ntype = payload
        .get("notification_type")
        .and_then(|v| v.as_str())
        .unwrap_or("?");
    parts.push(format!("type={}", ntype));
}

/// Format details for SessionStart events.
fn format_session_start_details(event: &Event, parts: &mut Vec<String>) {
    if let Some(payload) = parse_payload(event) {
        let source = payload
            .get("source")
            .and_then(|v| v.as_str())
            .unwrap_or("?");
        parts.push(format!("source={}", source));
    }

    if let Some(ref path) = event.transcript_path {
        let short_path = truncate_with_prefix(path, 40);
        parts.push(format!("transcript={}", short_path));
    }
}

/// Format details for ApiRequest events.
fn format_api_request_details(event: &Event, parts: &mut Vec<String>) {
    // For 0-token activity events, show metadata details instead of token counts
    if event.tokens_input == Some(0)
        && event.tokens_output == Some(0)
        && let Some(details) = format_activity_event_details(event)
    {
        parts.push(details);
        return;
    }

    // Show model if available
    if let Some(ref model) = event.model {
        // Shorten common model names
        let short_model = model
            .replace("claude-", "c-")
            .replace("sonnet", "son")
            .replace("opus", "op")
            .replace("haiku", "hai");
        parts.push(format!("model={}", short_model));
    }

    // Show token counts
    let tokens_in = event.tokens_input.unwrap_or(0);
    let tokens_out = event.tokens_output.unwrap_or(0);
    let total = tokens_in + tokens_out;
    parts.push(format!("in={} out={} ({})", tokens_in, tokens_out, total));

    // Show cache if present
    if let Some(cache_read) = event.tokens_cache_read
        && cache_read > 0
    {
        parts.push(format!("cache={}", cache_read));
    }

    // Show cost if available
    if let Some(cost) = event.cost_usd {
        parts.push(format!("${:.4}", cost));
    }
}

/// Format details for Codex activity events (0-token metadata events)
fn format_activity_event_details(event: &Event) -> Option<String> {
    let metadata = event.metadata.as_ref()?;
    let json: serde_json::Value = serde_json::from_str(metadata).ok()?;
    let event_type = json.get("event_type")?.as_str()?;

    let mut parts: Vec<String> = Vec::new();

    match event_type {
        "conversation_starts" => {
            if let Some(v) = json.get("approval_policy").and_then(|v| v.as_str()) {
                parts.push(format!("approval={}", v));
            }
            if let Some(v) = json.get("sandbox_policy").and_then(|v| v.as_str()) {
                parts.push(format!("sandbox={}", v));
            }
            if let Some(v) = json.get("provider").and_then(|v| v.as_str()) {
                parts.push(format!("provider={}", v));
            }
        }
        "user_prompt" => {
            if let Some(v) = json.get("prompt_length").and_then(|v| v.as_i64()) {
                parts.push(format!("len={}", v));
            }
        }
        "tool_decision" => {
            if let Some(v) = json.get("tool_name").and_then(|v| v.as_str()) {
                parts.push(format!("tool={}", v));
            }
            if let Some(v) = json.get("decision").and_then(|v| v.as_str()) {
                parts.push(format!("decision={}", v));
            }
        }
        "tool_result" => {
            if let Some(v) = json.get("tool_name").and_then(|v| v.as_str()) {
                parts.push(format!("tool={}", v));
            }
            if let Some(v) = json.get("success").and_then(|v| v.as_bool()) {
                parts.push(if v {
                    "ok".to_string()
                } else {
                    "FAIL".to_string()
                });
            }
            if let Some(v) = json.get("duration_ms").and_then(|v| v.as_i64()) {
                parts.push(format!("{}ms", v));
            }
        }
        "api_request" => {
            if let Some(v) = json.get("status_code").and_then(|v| v.as_i64()) {
                parts.push(format!("status={}", v));
            }
            if let Some(v) = json.get("attempt").and_then(|v| v.as_i64())
                && v > 1
            {
                parts.push(format!("attempt={}", v));
            }
            if let Some(v) = json.get("duration_ms").and_then(|v| v.as_i64()) {
                parts.push(format!("{}ms", v));
            }
        }
        _ => return None,
    }

    if parts.is_empty() {
        None
    } else {
        Some(parts.join(" "))
    }
}

/// Format details for other/unknown event types.
fn format_default_details(event: &Event, parts: &mut Vec<String>) {
    if let Some(ref cwd) = event.cwd {
        let short_cwd = truncate_with_prefix(cwd, 30);
        parts.push(format!("cwd={}", short_cwd));
    }
}

fn format_details(event: &Event) -> String {
    let mut parts: Vec<String> = Vec::new();

    // Show permission_mode if not default
    if let Some(ref mode) = event.permission_mode
        && mode != "default"
    {
        parts.push(format!("mode={}", mode));
    }

    // Add event-type-specific details
    match &event.event_type {
        EventType::PreToolUse | EventType::PostToolUse => {
            format_tool_use_details(event, &mut parts);
        }
        EventType::Notification => format_notification_details(event, &mut parts),
        EventType::SessionStart => format_session_start_details(event, &mut parts),
        EventType::ApiRequest => format_api_request_details(event, &mut parts),
        _ => format_default_details(event, &mut parts),
    }

    parts.join(" ")
}

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

    #[test]
    fn test_truncate_str_ascii() {
        assert_eq!(truncate_str("hello_world", 5), "hello");
        assert_eq!(truncate_str("short", 10), "short");
        assert_eq!(truncate_str("exact", 5), "exact");
        assert_eq!(truncate_str("", 5), "");
    }

    #[test]
    fn test_truncate_str_utf8() {
        // Test with multi-byte UTF-8 characters (emoji is 4 bytes)
        assert_eq!(truncate_str("session_🔥", 8), "session_");
        assert_eq!(truncate_str("🔥🔥🔥", 2), "🔥🔥");
        assert_eq!(truncate_str("hello🔥world", 6), "hello🔥");
    }

    #[test]
    fn test_truncate_str_edge_cases() {
        assert_eq!(truncate_str("hello", 0), "");
        assert_eq!(truncate_str("", 0), "");
        // Characters with multiple bytes (Chinese characters are 3 bytes each)
        assert_eq!(truncate_str("你好世界", 2), "你好");
    }
}
