use crate::protocol::{
    HistoryMessage, MessageAttachment, SessionSummary, SlashCommandInfo, UploadDescriptor,
};
use crate::session::{TurnSnapshot, TurnStatus};
use std::collections::VecDeque;
use tokio_tungstenite::tungstenite::Message;

const MAX_MESSAGES: usize = 2_000;
const MAX_INPUT_BYTES: usize = 16 * 1024;

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum MessageRole {
    User,
    Assistant,
    System,
}

#[derive(Debug, Clone)]
pub struct ChatMessage {
    pub id: String,
    pub role: MessageRole,
    pub content: String,
    pub reasoning_content: Option<String>,
    pub completion_status: crate::bus::CompletionStatus,
    pub attachments: Vec<MessageAttachment>,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Focus {
    Input,
    Sessions,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ConfirmAction {
    Archive,
    Delete,
    ClearHistory,
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Modal {
    Rename { input: String, cursor: usize },
    AttachPath { input: String, cursor: usize },
    Confirm(ConfirmAction),
}

pub struct App {
    pub ws_sender: Option<
        futures_util::stream::SplitSink<
            tokio_tungstenite::WebSocketStream<
                tokio_tungstenite::MaybeTlsStream<tokio::net::TcpStream>,
            >,
            Message,
        >,
    >,
    pub ws_receiver: Option<
        futures_util::stream::SplitStream<
            tokio_tungstenite::WebSocketStream<
                tokio_tungstenite::MaybeTlsStream<tokio::net::TcpStream>,
            >,
        >,
    >,
    pub current_session_id: Option<String>,
    pub sessions: Vec<SessionSummary>,
    pub selected_session: usize,
    pub show_archived: bool,
    pub messages: VecDeque<ChatMessage>,
    pub history_revision: i64,
    pub active_turn: Option<TurnSnapshot>,
    pub input: String,
    /// UTF-8 byte offset. It is always maintained at a character boundary.
    pub input_cursor_pos: usize,
    pub focus: Focus,
    pub modal: Option<Modal>,
    pub show_help: bool,
    pub chat_scroll_from_bottom: u16,
    pub should_quit: bool,
    pub pending_quit: bool,
    pub connected: bool,
    pub pending_responses: usize,
    pub status_message: Option<String>,
    pub commands: Vec<SlashCommandInfo>,
    pub show_command_menu: bool,
    pub selected_command_idx: usize,
    pub http_base_url: String,
    pub auth_token: Option<String>,
    pub client_id: String,
    pub file_transfer_supported: bool,
    pub pending_uploads: Vec<UploadDescriptor>,
}

impl App {
    pub fn new() -> Self {
        Self {
            ws_sender: None,
            ws_receiver: None,
            current_session_id: None,
            sessions: Vec::new(),
            selected_session: 0,
            show_archived: false,
            messages: VecDeque::new(),
            history_revision: 0,
            active_turn: None,
            input: String::new(),
            input_cursor_pos: 0,
            focus: Focus::Input,
            modal: None,
            show_help: false,
            chat_scroll_from_bottom: 0,
            should_quit: false,
            pending_quit: false,
            connected: true,
            pending_responses: 0,
            status_message: Some("正在加载会话…".to_string()),
            commands: Vec::new(),
            show_command_menu: false,
            selected_command_idx: 0,
            http_base_url: String::new(),
            auth_token: None,
            client_id: String::new(),
            file_transfer_supported: false,
            pending_uploads: Vec::new(),
        }
    }

    pub fn add_message(&mut self, role: MessageRole, content: String) {
        self.add_message_with_attachments(crate::util::short_id(), role, content, Vec::new());
    }

    pub fn add_message_with_attachments(
        &mut self,
        id: String,
        role: MessageRole,
        content: String,
        attachments: Vec<MessageAttachment>,
    ) {
        self.messages.push_back(ChatMessage {
            id,
            role,
            content,
            reasoning_content: None,
            completion_status: crate::bus::CompletionStatus::Completed,
            attachments,
        });
        while self.messages.len() > MAX_MESSAGES {
            self.messages.pop_front();
        }
        self.chat_scroll_from_bottom = 0;
    }

    pub fn set_history(&mut self, session_id: &str, messages: Vec<HistoryMessage>) {
        if self.current_session_id.as_deref() != Some(session_id) {
            return;
        }
        let history_revision = messages
            .iter()
            .map(|message| message.seq)
            .max()
            .unwrap_or(0);
        let calibrates_terminal = self.active_turn.as_ref().is_some_and(|turn| {
            turn.status != TurnStatus::Running
                && messages.iter().any(|message| message.id == turn.message_id)
        });
        self.messages = messages
            .into_iter()
            .filter_map(|message| {
                let role = match message.role.as_str() {
                    "user" => MessageRole::User,
                    "assistant" => MessageRole::Assistant,
                    "system" | "tool" => MessageRole::System,
                    _ => return None,
                };
                Some(ChatMessage {
                    id: message.id,
                    role,
                    content: message.content,
                    reasoning_content: message.reasoning_content,
                    completion_status: message.completion_status,
                    attachments: message.attachments,
                })
            })
            .collect();
        while self.messages.len() > MAX_MESSAGES {
            self.messages.pop_front();
        }
        self.chat_scroll_from_bottom = 0;
        self.history_revision = history_revision;
        self.status_message = None;
        if calibrates_terminal {
            self.active_turn = None;
        }
    }

    pub fn apply_turn_commit(
        &mut self,
        session_id: &str,
        history_revision: i64,
        messages: Vec<HistoryMessage>,
    ) -> bool {
        if self.current_session_id.as_deref() != Some(session_id)
            || history_revision <= self.history_revision
        {
            return false;
        }
        let calibrates_terminal = self.active_turn.as_ref().is_some_and(|turn| {
            turn.status != TurnStatus::Running
                && messages.iter().any(|message| message.id == turn.message_id)
        });
        for message in messages {
            let role = match message.role.as_str() {
                "user" => MessageRole::User,
                "assistant" => MessageRole::Assistant,
                "system" | "tool" => MessageRole::System,
                _ => continue,
            };
            let projected = ChatMessage {
                id: message.id.clone(),
                role,
                content: message.content,
                reasoning_content: message.reasoning_content,
                completion_status: message.completion_status,
                attachments: message.attachments,
            };
            if let Some(existing) = self
                .messages
                .iter_mut()
                .find(|existing| existing.id == message.id)
            {
                *existing = projected;
            } else {
                self.messages.push_back(projected);
            }
        }
        while self.messages.len() > MAX_MESSAGES {
            self.messages.pop_front();
        }
        self.history_revision = history_revision;
        self.chat_scroll_from_bottom = 0;
        self.status_message = None;
        if calibrates_terminal {
            self.active_turn = None;
        }
        true
    }

    pub fn set_sessions(&mut self, sessions: Vec<SessionSummary>) {
        self.sessions = sessions;
        if let Some(current) = &self.current_session_id
            && let Some(index) = self
                .sessions
                .iter()
                .position(|session| &session.session_id == current)
        {
            self.selected_session = index;
        }
        self.clamp_session_selection();
    }

    pub fn set_current_session(&mut self, session_id: Option<String>) {
        if self.current_session_id != session_id {
            self.current_session_id = session_id;
            self.messages.clear();
            self.active_turn = None;
            self.history_revision = 0;
            self.pending_uploads.clear();
            self.chat_scroll_from_bottom = 0;
        }
        if let Some(current) = &self.current_session_id
            && let Some(index) = self
                .sessions
                .iter()
                .position(|session| &session.session_id == current)
        {
            self.selected_session = index;
        }
    }

    pub fn apply_turn_snapshot(&mut self, snapshot: TurnSnapshot) -> bool {
        if self.current_session_id.as_deref() != Some(&snapshot.session_id) {
            return false;
        }
        if let Some(current) = &self.active_turn
            && current.id == snapshot.id
            && current.revision >= snapshot.revision
        {
            return false;
        }
        let already_committed = snapshot.status != TurnStatus::Running
            && self
                .messages
                .iter()
                .any(|message| message.id == snapshot.message_id);
        self.active_turn = (!already_committed).then_some(snapshot);
        self.chat_scroll_from_bottom = 0;
        true
    }

    pub fn current_title(&self) -> &str {
        self.current_session_id
            .as_ref()
            .and_then(|id| {
                self.sessions
                    .iter()
                    .find(|session| &session.session_id == id)
            })
            .map(|session| session.title.as_str())
            .unwrap_or("新对话")
    }

    pub fn selected_session_id(&self) -> Option<String> {
        self.sessions
            .get(self.selected_session)
            .map(|session| session.session_id.clone())
    }

    pub fn select_next_session(&mut self) {
        if !self.sessions.is_empty() {
            self.selected_session = (self.selected_session + 1).min(self.sessions.len() - 1);
        }
    }

    pub fn select_previous_session(&mut self) {
        self.selected_session = self.selected_session.saturating_sub(1);
    }

    fn clamp_session_selection(&mut self) {
        self.selected_session = self
            .selected_session
            .min(self.sessions.len().saturating_sub(1));
    }

    pub fn scroll_chat_up(&mut self, lines: u16) {
        self.chat_scroll_from_bottom = self.chat_scroll_from_bottom.saturating_add(lines);
    }

    pub fn scroll_chat_down(&mut self, lines: u16) {
        self.chat_scroll_from_bottom = self.chat_scroll_from_bottom.saturating_sub(lines);
    }

    pub fn input_insert_char(&mut self, c: char) {
        if self.input.len() + c.len_utf8() <= MAX_INPUT_BYTES {
            self.input.insert(self.input_cursor_pos, c);
            self.input_cursor_pos += c.len_utf8();
        }
    }

    pub fn input_insert_str(&mut self, text: &str) {
        let remaining = MAX_INPUT_BYTES.saturating_sub(self.input.len());
        let mut end = text.len().min(remaining);
        while !text.is_char_boundary(end) {
            end -= 1;
        }
        self.input.insert_str(self.input_cursor_pos, &text[..end]);
        self.input_cursor_pos += end;
    }

    pub fn input_delete_char(&mut self) {
        if let Some(previous) = previous_boundary(&self.input, self.input_cursor_pos) {
            self.input.drain(previous..self.input_cursor_pos);
            self.input_cursor_pos = previous;
        }
    }

    pub fn input_delete_forward(&mut self) {
        if let Some(next) = next_boundary(&self.input, self.input_cursor_pos) {
            self.input.drain(self.input_cursor_pos..next);
        }
    }

    pub fn input_move_cursor_left(&mut self) {
        if let Some(previous) = previous_boundary(&self.input, self.input_cursor_pos) {
            self.input_cursor_pos = previous;
        }
    }

    pub fn input_move_cursor_right(&mut self) {
        if let Some(next) = next_boundary(&self.input, self.input_cursor_pos) {
            self.input_cursor_pos = next;
        }
    }

    pub fn input_move_cursor_to_start(&mut self) {
        let line_start = self.input[..self.input_cursor_pos]
            .rfind('\n')
            .map_or(0, |index| index + 1);
        self.input_cursor_pos = line_start;
    }

    pub fn input_move_cursor_to_end(&mut self) {
        let tail = &self.input[self.input_cursor_pos..];
        self.input_cursor_pos += tail.find('\n').unwrap_or(tail.len());
    }

    pub fn take_input(&mut self) -> String {
        let input = std::mem::take(&mut self.input);
        self.input_cursor_pos = 0;
        input
    }

    pub fn handle_ctrl_c_for_quit(&mut self) {
        if self.pending_quit {
            self.should_quit = true;
        } else {
            self.pending_quit = true;
            self.status_message = Some("再次按 Ctrl+C 退出".to_string());
        }
    }

    pub fn cancel_pending_quit(&mut self) {
        if self.pending_quit {
            self.pending_quit = false;
            self.status_message = None;
        }
    }

    pub fn set_commands(&mut self, commands: Vec<SlashCommandInfo>) {
        self.commands = commands;
    }

    pub fn get_filtered_commands(&self) -> Vec<&SlashCommandInfo> {
        let query = self
            .input
            .split_whitespace()
            .next()
            .unwrap_or("")
            .to_lowercase();
        self.commands
            .iter()
            .filter(|command| {
                command.name.to_lowercase().contains(&query)
                    || command.description.to_lowercase().contains(&query)
                    || command
                        .aliases
                        .iter()
                        .any(|alias| alias.to_lowercase().starts_with(&query))
            })
            .collect()
    }

    pub fn select_next_command(&mut self) {
        let len = self.get_filtered_commands().len();
        if len > 0 {
            self.selected_command_idx = (self.selected_command_idx + 1) % len;
        }
    }

    pub fn select_previous_command(&mut self) {
        let len = self.get_filtered_commands().len();
        if len > 0 {
            self.selected_command_idx = (self.selected_command_idx + len - 1) % len;
        }
    }

    pub fn insert_selected_command(&mut self) {
        let command = self
            .get_filtered_commands()
            .get(self.selected_command_idx)
            .and_then(|command| command.aliases.first().cloned());
        if let Some(command) = command {
            self.input = format!("{command} ");
            self.input_cursor_pos = self.input.len();
        }
    }
}

fn previous_boundary(value: &str, offset: usize) -> Option<usize> {
    value[..offset]
        .char_indices()
        .next_back()
        .map(|(index, _)| index)
}

fn next_boundary(value: &str, offset: usize) -> Option<usize> {
    value[offset..]
        .chars()
        .next()
        .map(|character| offset + character.len_utf8())
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::session::{TurnId, TurnPhase, TurnState};

    fn turn(revision: u64, status: TurnStatus) -> TurnSnapshot {
        TurnState {
            id: TurnId("turn".into()),
            session_id: "current".into(),
            message_id: "message".into(),
            revision,
            status,
            phase: TurnPhase::Responding,
            blocks: Vec::new(),
            usage: None,
            error: None,
        }
    }

    #[test]
    fn unicode_cursor_edits_only_at_character_boundaries() {
        let mut app = App::new();
        app.input_insert_str("你a🙂");
        app.input_move_cursor_left();
        app.input_delete_char();
        assert_eq!(app.input, "你🙂");
        assert!(app.input.is_char_boundary(app.input_cursor_pos));
        app.input_delete_forward();
        assert_eq!(app.input, "你");
    }

    #[test]
    fn stale_history_does_not_replace_the_active_dialog() {
        let mut app = App::new();
        app.set_current_session(Some("new".to_string()));
        app.add_message(MessageRole::User, "keep".to_string());
        app.set_history("old", Vec::new());
        assert_eq!(app.messages.len(), 1);
    }

    #[test]
    fn active_turn_ignores_stale_revisions_and_other_sessions() {
        let mut app = App::new();
        app.set_current_session(Some("current".into()));

        assert!(app.apply_turn_snapshot(turn(2, TurnStatus::Running)));
        assert!(!app.apply_turn_snapshot(turn(1, TurnStatus::Running)));
        let mut other = turn(3, TurnStatus::Running);
        other.session_id = "other".into();
        assert!(!app.apply_turn_snapshot(other));
        assert_eq!(app.active_turn.as_ref().unwrap().revision, 2);
    }

    #[test]
    fn terminal_turn_remains_visible_until_history_calibrates_it() {
        let mut app = App::new();
        app.set_current_session(Some("current".into()));
        app.apply_turn_snapshot(turn(3, TurnStatus::Completed));

        assert!(app.active_turn.is_some());
        app.set_history(
            "current",
            vec![HistoryMessage {
                id: "message".into(),
                seq: 1,
                role: "assistant".into(),
                content: "done".into(),
                reasoning_content: None,
                completion_status: crate::bus::CompletionStatus::Completed,
                created_at: 1,
                tool_call_id: None,
                tool_name: None,
                tool_calls: None,
                attachments: Vec::new(),
                turn_origin: crate::bus::TurnOrigin::User,
            }],
        );
        assert!(app.active_turn.is_none());
    }

    #[test]
    fn committed_delta_calibrates_terminal_without_reloading_history() {
        let mut app = App::new();
        app.set_current_session(Some("current".into()));
        app.apply_turn_snapshot(turn(3, TurnStatus::Completed));

        assert!(app.apply_turn_commit(
            "current",
            2,
            vec![HistoryMessage {
                id: "message".into(),
                seq: 2,
                role: "assistant".into(),
                content: "done".into(),
                reasoning_content: None,
                completion_status: crate::bus::CompletionStatus::Completed,
                created_at: 1,
                tool_call_id: None,
                tool_name: None,
                tool_calls: None,
                attachments: Vec::new(),
                turn_origin: crate::bus::TurnOrigin::User,
            }],
        ));
        assert!(app.active_turn.is_none());
        assert_eq!(app.messages.back().unwrap().content, "done");
        assert!(!app.apply_turn_commit("current", 2, Vec::new()));
    }

    #[test]
    fn terminal_snapshot_calibrates_when_commit_arrived_first() {
        let mut app = App::new();
        app.set_current_session(Some("current".into()));
        assert!(app.apply_turn_commit(
            "current",
            2,
            vec![HistoryMessage {
                id: "message".into(),
                seq: 2,
                role: "assistant".into(),
                content: "done".into(),
                reasoning_content: None,
                completion_status: crate::bus::CompletionStatus::Completed,
                created_at: 1,
                tool_call_id: None,
                tool_name: None,
                tool_calls: None,
                attachments: Vec::new(),
                turn_origin: crate::bus::TurnOrigin::User,
            }],
        ));

        assert!(app.apply_turn_snapshot(turn(3, TurnStatus::Completed)));
        assert!(app.active_turn.is_none());
    }

    #[test]
    fn failed_turn_without_a_durable_message_remains_visible_after_history_refresh() {
        let mut app = App::new();
        app.set_current_session(Some("current".into()));
        app.apply_turn_snapshot(turn(3, TurnStatus::Failed));

        app.set_history("current", Vec::new());

        assert_eq!(
            app.active_turn.as_ref().map(|turn| turn.status),
            Some(TurnStatus::Failed)
        );
    }
}
