//! Session lifecycle and event handling for TUI application.
//!
//! Contains session management methods and `SessionEvent` handlers.

use std::path::Path;
use std::sync::Arc;

use ratatui::widgets::ListState;

use crate::error::SessionError;
use crate::session::{SessionCommand, SessionEvent, SessionId, SessionStatus};
use crate::tui::popup::{ActionSelectPopupState, IssueItem, LoadingOperation, WorktreeItem};

use super::state::{AppState, SessionSnapshot};
use super::tuiapp::TuiApp;

/// Adjust list selection to valid bounds after item removal.
fn adjust_list_selection(list_state: &mut ListState, len: usize) {
    if let Some(selected) = list_state.selected() {
        if selected >= len && len > 0 {
            list_state.select(Some(len - 1));
        } else if len == 0 {
            list_state.select(None);
        }
    }
}

impl TuiApp {
    /// Resize terminal and forward to PTY processes
    pub(super) async fn resize_terminal(&mut self, rows: u16, cols: u16) {
        self.size = (rows, cols);
        // Resize vt100 parsers
        for parser in self.terminal_buffers.values_mut() {
            parser.set_size(rows, cols);
        }
        // Forward resize to PTY processes
        for session in &self.sessions {
            let _ = self
                .cmd_tx
                .send(SessionCommand::Resize {
                    id: session.id,
                    rows,
                    cols,
                })
                .await;
        }
    }

    /// Transition to error popup state (legacy)
    pub(super) fn show_error(&mut self, message: String) {
        let from_popup = matches!(self.state, AppState::WorkspacePopup);
        self.state = AppState::ErrorPopup {
            message,
            from_popup,
        };
    }

    /// Context-aware error transition
    pub(super) fn transition_to_error(&mut self, message: String) {
        use super::state::FlowContext;
        let from_popup = !matches!(self.state.flow_context(), FlowContext::Normal);
        self.state = AppState::ErrorPopup {
            message,
            from_popup,
        };
    }

    /// Return to base state after error/popup dismissal
    pub(super) fn return_to_base(&mut self) {
        use super::state::FlowContext;
        self.state = match self.state.flow_context() {
            FlowContext::IssueFlow | FlowContext::WorkspaceFlow => AppState::WorkspacePopup,
            FlowContext::Normal => AppState::Normal,
        };
        self.cleanup_flow_state();
    }

    /// Reset transient flow state
    pub(super) fn cleanup_flow_state(&mut self) {
        self.action_select_state = crate::tui::popup::ActionSelectPopupState::new();
    }

    /// Cancel issue flow and return to workspace popup
    pub(super) fn cancel_issue_flow(&mut self) {
        self.cleanup_flow_state();
        self.state = AppState::WorkspacePopup;
    }

    /// Create session with optional branch name
    ///
    /// If branch is Some, creates worktree with that branch and starts session.
    /// If branch is None, auto-generates branch from session ID.
    pub(super) async fn create_session_with_branch(
        &mut self,
        branch: Option<String>,
    ) -> Result<(), SessionError> {
        let (response_tx, response_rx) = tokio::sync::oneshot::channel();
        self.cmd_tx
            .send(SessionCommand::Create {
                cmd: "claude".to_string(),
                args: self.default_args.clone(),
                cwd: None,
                branch_name: branch,
                rows: self.size.0,
                cols: self.size.1,
                response_tx,
            })
            .await
            .map_err(|_| {
                SessionError::SpawnFailed(std::io::Error::new(
                    std::io::ErrorKind::BrokenPipe,
                    "Failed to send create command",
                ))
            })?;

        // Wait for response
        if let Ok(result) = response_rx.await {
            result?;
        }
        Ok(())
    }

    /// Adopt existing worktree (start session in it)
    pub(super) async fn adopt_worktree(&mut self, path: &Path) -> Result<(), SessionError> {
        let (response_tx, response_rx) = tokio::sync::oneshot::channel();
        self.cmd_tx
            .send(SessionCommand::CreateFromWorktree {
                worktree_path: path.to_path_buf(),
                cmd: "claude".to_string(),
                args: self.default_args.clone(),
                rows: self.size.0,
                cols: self.size.1,
                response_tx,
            })
            .await
            .map_err(|_| {
                SessionError::SpawnFailed(std::io::Error::new(
                    std::io::ErrorKind::BrokenPipe,
                    "Failed to send adopt command",
                ))
            })?;

        if let Ok(result) = response_rx.await {
            result?;
        }
        Ok(())
    }

    /// Close terminated session (remove from list + cleanup worktree)
    pub(super) async fn close_session(&mut self, id: SessionId) {
        let (response_tx, response_rx) = tokio::sync::oneshot::channel();
        if self
            .cmd_tx
            .send(SessionCommand::CloseSession { id, response_tx })
            .await
            .is_err()
        {
            self.show_error("Failed to send close command".to_string());
            return;
        }

        // Wait for response
        match response_rx.await {
            Ok(Ok(_worktree_path)) => {
                // Remove from local list
                self.sessions.retain(|s| s.id != id);
                self.terminal_buffers.remove(&id);
                self.scroll_offsets.remove(&id);
                self.pending_sessions.remove(&id);

                // Unregister from NotificationManager
                self.notification_manager
                    .lock()
                    .await
                    .unregister_session(&id);

                // Adjust active_idx if needed
                if self.active_idx >= self.sessions.len() && !self.sessions.is_empty() {
                    self.active_idx = self.sessions.len() - 1;
                }

                // Adjust popup selection if needed
                adjust_list_selection(&mut self.popup_state.session_list, self.sessions.len());
            }
            Ok(Err(e)) => self.show_error(e.to_string()),
            Err(_) => self.show_error("Session manager disconnected".to_string()),
        }
    }

    /// Refresh worktrees list from `SessionManager`
    pub(super) async fn refresh_worktrees(&mut self) {
        let (response_tx, response_rx) = tokio::sync::oneshot::channel();
        if self
            .cmd_tx
            .send(SessionCommand::ListWorktrees { response_tx })
            .await
            .is_err()
        {
            return;
        }

        // Wait for response with timeout to avoid blocking
        let timeout = tokio::time::timeout(std::time::Duration::from_millis(100), response_rx);
        if let Ok(Ok(worktree_infos)) = timeout.await {
            // Convert WorktreeInfo to WorktreeItem
            self.worktrees = worktree_infos
                .into_iter()
                .map(|info| WorktreeItem::new(info.branch, info.path, info.status))
                .collect();
        }
    }

    pub(super) async fn terminate_current_session(&mut self) -> Result<(), SessionError> {
        if let Some(session) = self.sessions.get(self.active_idx) {
            let id = session.id;
            let (response_tx, response_rx) = tokio::sync::oneshot::channel();
            self.cmd_tx
                .send(SessionCommand::Terminate { id, response_tx })
                .await
                .map_err(|_| {
                    SessionError::SpawnFailed(std::io::Error::new(
                        std::io::ErrorKind::BrokenPipe,
                        "Failed to send terminate command",
                    ))
                })?;

            if let Ok(result) = response_rx.await {
                result?;
            }
        }
        Ok(())
    }

    pub(super) fn switch_session(&mut self, idx: usize) {
        if idx < self.sessions.len() {
            self.active_idx = idx;
            // Note: pending status is NOT cleared on switch; only cleared when user presses Enter
        }
    }

    pub(super) fn next_session(&mut self) {
        if !self.sessions.is_empty() {
            self.active_idx = (self.active_idx + 1) % self.sessions.len();
            // Note: pending status is NOT cleared on next; only cleared when user presses Enter
        }
    }

    pub(super) fn prev_session(&mut self) {
        if !self.sessions.is_empty() {
            self.active_idx = if self.active_idx == 0 {
                self.sessions.len() - 1
            } else {
                self.active_idx - 1
            };
            // Note: pending status is NOT cleared on prev; only cleared when user presses Enter
        }
    }

    pub(super) async fn send_input(&mut self, data: &[u8]) {
        let Some(session_id) = self.sessions.get(self.active_idx).map(|s| s.id) else {
            return;
        };

        // Clear pending status when user presses Enter (submitting response)
        if data.contains(&b'\r') || data.contains(&b'\n') {
            self.clear_pending(&session_id);
        }

        if let Err(e) = self
            .cmd_tx
            .send(SessionCommand::SendInput {
                id: session_id,
                data: data.to_vec(),
            })
            .await
        {
            tracing::error!("Failed to send input: {e}");
        }
    }

    pub(super) fn scroll(&mut self, delta: i32) {
        if let Some(session) = self.sessions.get(self.active_idx) {
            let offset = self.scroll_offsets.entry(session.id).or_insert(0);
            if delta < 0 {
                *offset = offset.saturating_add(delta.unsigned_abs() as usize);
            } else {
                *offset = offset.saturating_sub(delta.unsigned_abs() as usize);
            }
        }
    }

    /// Handle session event from manager
    pub fn handle_session_event(&mut self, event: SessionEvent) {
        match event {
            // Session lifecycle
            SessionEvent::Created {
                id,
                branch,
                auto_input,
            } => self.handle_created(id, branch, auto_input),
            SessionEvent::Output { id, data } => self.handle_output(id, &data),
            SessionEvent::TitleChanged { id, title } => self.handle_title_changed(id, title),
            SessionEvent::Terminated { id, exit_code } => self.handle_terminated(id, exit_code),
            SessionEvent::Error { id, error } => tracing::error!("Session {id} error: {error}"),
            // Worktree operations
            SessionEvent::WorktreesRefreshed {
                worktrees,
                fetch_pending,
            } => {
                self.handle_worktrees_refreshed(worktrees, fetch_pending);
            }
            SessionEvent::WorktreeDeleted { path, result } => {
                self.handle_worktree_deleted(&path, result);
            }
            SessionEvent::WorktreePulled { path, result } => {
                self.handle_worktree_pulled(&path, result);
            }
            // Hook notifications
            SessionEvent::HookReceived { event } => self.handle_hook_received(&event),
            // Issue flow
            SessionEvent::IssuesFetched { result } => self.handle_issues_fetched(result),
            SessionEvent::IssueFetched { issue_number } => self.handle_issue_fetched(issue_number),
            SessionEvent::IssueActionsFetched {
                issue_number,
                result,
            } => {
                self.handle_issue_actions_fetched(issue_number, result);
            }
            // Cost tracking
            SessionEvent::CostFetched { cost } => {
                self.today_cost = cost;
            }
        }
    }

    // === Session event handlers ===

    fn handle_created(
        &mut self,
        id: SessionId,
        branch: Option<String>,
        auto_input: Vec<crate::session::AutoInputStep>,
    ) {
        let name = format!("session-{}", self.sessions.len() + 1);

        // Register session name with NotificationManager for webhook messages
        // Only spawn if Tokio runtime is available (not in sync tests)
        if let Ok(handle) = tokio::runtime::Handle::try_current() {
            let manager = Arc::clone(&self.notification_manager);
            let name_clone = name.clone();
            handle.spawn(async move {
                manager.lock().await.register_session(id, name_clone);
            });
        }

        self.sessions.push(SessionSnapshot {
            id,
            name,
            status: SessionStatus::Running,
            branch,
        });
        if !auto_input.is_empty() {
            self.pending_auto_input = Some((id, auto_input));
            self.auto_input_ready = false;
            self.auto_input_next_at = None;
        }
        self.terminal_buffers
            .insert(id, vt100::Parser::new(self.size.0, self.size.1, 1000));
        self.active_idx = self.sessions.len() - 1;
    }

    fn handle_output(&mut self, id: SessionId, data: &[u8]) {
        // Note: pending status is NOT cleared on output; only cleared when user presses Enter
        if let Some(parser) = self.terminal_buffers.get_mut(&id) {
            parser.process(data);
        }
        // Follow mode: sessions at bottom (offset=0 or absent) stay at bottom
        // Scrolled back: preserve position (no scroll_offsets.insert here)
        // Detect "Claude Code" for auto-input ready state
        if let Some((session_id, _)) = &self.pending_auto_input
            && *session_id == id
            && !self.auto_input_ready
        {
            const READY_MARKER: &[u8] = b"Claude Code";
            if data.windows(READY_MARKER.len()).any(|w| w == READY_MARKER) {
                self.auto_input_ready = true;
                self.auto_input_next_at =
                    Some(std::time::Instant::now() + std::time::Duration::from_millis(100));
            }
        }
    }

    fn handle_title_changed(&mut self, id: SessionId, title: String) {
        if let Some(session) = self.sessions.iter_mut().find(|s| s.id == id) {
            session.name.clone_from(&title);

            // Update session name in NotificationManager
            // Only spawn if Tokio runtime is available (not in sync tests)
            if let Ok(handle) = tokio::runtime::Handle::try_current() {
                let manager = Arc::clone(&self.notification_manager);
                handle.spawn(async move {
                    manager.lock().await.register_session(id, title);
                });
            }
        }
    }

    fn handle_terminated(&mut self, id: SessionId, exit_code: Option<i32>) {
        if let Some(session) = self.sessions.iter_mut().find(|s| s.id == id) {
            session.status = SessionStatus::Terminated { exit_code };
        }
        if matches!(self.state, AppState::Normal)
            && self
                .sessions
                .get(self.active_idx)
                .is_some_and(|s| s.id == id)
        {
            self.state = AppState::SessionTerminatedPopup {
                session_id: id,
                exit_code,
            };
        }
    }

    // === Worktree event handlers ===

    fn handle_worktrees_refreshed(
        &mut self,
        worktrees: Vec<crate::worktree::WorktreeInfo>,
        fetch_pending: bool,
    ) {
        self.worktrees = worktrees
            .into_iter()
            .map(|info| WorktreeItem::new(info.branch, info.path, info.status))
            .collect();
        // Only clear loading when fetch is complete
        if !fetch_pending {
            self.popup_state.loading = LoadingOperation::Idle;
        }
        if !self.worktrees.is_empty() && self.popup_state.worktree_list.selected().is_none() {
            self.popup_state.worktree_list.select(Some(0));
        }
    }

    fn handle_worktree_deleted(&mut self, path: &std::path::Path, result: Result<(), String>) {
        self.popup_state.loading = LoadingOperation::Idle;
        match result {
            Ok(()) => {
                self.worktrees.retain(|w| w.path != path);
                adjust_list_selection(&mut self.popup_state.worktree_list, self.worktrees.len());
            }
            Err(e) => self.show_error(e),
        }
    }

    fn handle_worktree_pulled(
        &mut self,
        path: &std::path::Path,
        result: Result<crate::worktree::GitWorktreeStatus, String>,
    ) {
        match result {
            Ok(status) => {
                if let Some(item) = self.worktrees.iter_mut().find(|w| w.path == path) {
                    item.status = status;
                }
            }
            Err(e) => self.show_error(e),
        }
        self.popup_state.loading = LoadingOperation::Idle;
    }

    fn handle_hook_received(&mut self, event: &crate::hooks::HookEvent) {
        if event.requires_attention() {
            self.mark_pending(&event.session_id);
        }

        // Forward to NotificationManager (handles bell + webhook)
        // Only spawn if Tokio runtime is available (not in sync tests)
        if let Ok(handle) = tokio::runtime::Handle::try_current() {
            let event_clone = event.clone();
            let manager = Arc::clone(&self.notification_manager);
            handle.spawn(async move {
                manager.lock().await.handle(event_clone).await;
            });
        }

        tracing::debug!(
            "Received hook event: {:?} from session {}",
            event.event_type,
            event.session_id
        );
    }

    // === Issue event handlers ===

    fn handle_issues_fetched(&mut self, result: Result<Vec<crate::github::GitHubIssue>, String>) {
        match result {
            Ok(issues) => {
                self.issues = issues
                    .into_iter()
                    .map(|i| IssueItem::new(i.number, &i.title, i.labels_display()))
                    .collect();
                if !self.issues.is_empty() {
                    self.popup_state.issue_list.select(Some(0));
                }
            }
            Err(e) => tracing::warn!("Failed to fetch issues: {e}"),
        }
    }

    fn handle_issue_fetched(&mut self, issue_number: u32) {
        if let AppState::IssueLoading {
            issue_number: current,
            ..
        } = self.state
            && current == issue_number
        {
            self.state = AppState::IssueLoading {
                issue_number,
                phase: LoadingOperation::GeneratingActions { issue_number },
            };
        }
    }

    fn handle_issue_actions_fetched(
        &mut self,
        issue_number: u32,
        result: Result<Vec<crate::github::ActionChoice>, String>,
    ) {
        match result {
            Ok(choices) if !choices.is_empty() => {
                self.state = AppState::ActionSelectPopup {
                    issue_number,
                    choices,
                };
                self.action_select_state = ActionSelectPopupState::new();
                self.action_select_state.list_state.select(Some(0));
            }
            Ok(_) => self.transition_to_error("No action choices generated".to_string()),
            Err(e) => self.transition_to_error(format!("Failed to generate actions: {e}")),
        }
    }

    /// Process pending auto-input for newly created sessions.
    ///
    /// Waits for "Claude Code" to appear in output before sending input.
    /// Sends input data to the PTY with configurable delays between steps.
    pub(super) async fn process_pending_auto_input(&mut self) {
        use std::time::{Duration, Instant};

        // Check if we have pending auto-input
        let Some((session_id, ref mut steps)) = self.pending_auto_input else {
            return;
        };

        // Wait for "Claude Code" to appear (ready state)
        if !self.auto_input_ready {
            return;
        }

        // Check if we're still waiting for delay (100ms after ready, or between steps)
        if let Some(next_at) = self.auto_input_next_at
            && Instant::now() < next_at
        {
            return;
        }

        // Get the first step
        let Some(step) = steps.first().cloned() else {
            // No more steps, clear pending
            self.pending_auto_input = None;
            self.auto_input_ready = false;
            self.auto_input_next_at = None;
            return;
        };

        // Send the input to PTY
        let _ = self
            .cmd_tx
            .send(SessionCommand::SendInput {
                id: session_id,
                data: step.data,
            })
            .await;

        // Remove the processed step
        steps.remove(0);

        // Set up next delay or clear if done
        if steps.is_empty() {
            self.pending_auto_input = None;
            self.auto_input_ready = false;
            self.auto_input_next_at = None;
        } else if let Some(next_step) = steps.first() {
            self.auto_input_next_at =
                Some(Instant::now() + Duration::from_millis(next_step.delay_ms));
        }
    }
}
