use std::borrow::Cow;

use crate::{action::Action, components::Component, errors::Result, models::TaskDetail, state::SearchHighlight, theme::Theme};
use ratatui::{
    prelude::*,
    widgets::{Block, BorderType, Borders, Clear, Padding, Paragraph, Scrollbar, ScrollbarOrientation, ScrollbarState},
};

pub struct TaskDetailPanel<'a> {
    task: &'a TaskDetail,
    scroll_offset: u16,
    highlight: Option<&'a SearchHighlight>,
    cached_lines: Option<&'a [Line<'static>]>,
    /// Clamped scroll value computed during draw, readable after render.
    effective_scroll: u16,
    /// The scrollbar track area computed during draw, for mouse hit-testing.
    scrollbar_area: Option<Rect>,
    /// Total content height (line count) computed during draw.
    content_height: u16,
    /// Visible viewport height (line count) computed during draw.
    visible_height: u16,
}

pub struct SearchableLine {
    pub display: String,
    pub search_text: String,
}

struct DetailLine {
    display: Line<'static>,
    display_text: String,
    search_text: String,
}

impl DetailLine {
    fn new(display: Line<'static>) -> Self {
        let display_text = line_to_string(&display);
        Self {
            display,
            search_text: display_text.clone(),
            display_text,
        }
    }
}

fn line_to_string(line: &Line) -> String {
    let mut text = String::new();
    for span in &line.spans {
        text.push_str(span.content.as_ref());
    }
    text
}

impl<'a> TaskDetailPanel<'a> {
    pub fn new(task: &'a TaskDetail, scroll_offset: u16, highlight: Option<&'a SearchHighlight>) -> Self {
        Self::with_cached_lines(task, scroll_offset, highlight, None)
    }

    pub fn with_cached_lines(
        task: &'a TaskDetail,
        scroll_offset: u16,
        highlight: Option<&'a SearchHighlight>,
        cached_lines: Option<&'a [Line<'static>]>,
    ) -> Self {
        Self {
            task,
            scroll_offset,
            highlight,
            cached_lines,
            effective_scroll: 0,
            scrollbar_area: None,
            content_height: 0,
            visible_height: 0,
        }
    }

    /// Return the clamped scroll value computed during the last draw call.
    pub fn effective_scroll(&self) -> u16 {
        self.effective_scroll
    }

    /// Return the scrollbar track area computed during the last draw call.
    pub fn scrollbar_area(&self) -> Option<Rect> {
        self.scrollbar_area
    }

    /// Return the total content height computed during the last draw call.
    pub fn content_height(&self) -> u16 {
        self.content_height
    }

    /// Return the visible viewport height computed during the last draw call.
    pub fn visible_height(&self) -> u16 {
        self.visible_height
    }

    /// Calculate centered rect for the detail modal
    fn centered_rect(percent_x: u16, percent_y: u16, area: Rect) -> Rect {
        let popup_layout = Layout::default()
            .direction(Direction::Vertical)
            .constraints([
                Constraint::Percentage((100 - percent_y) / 2),
                Constraint::Percentage(percent_y),
                Constraint::Percentage((100 - percent_y) / 2),
            ])
            .split(area);

        Layout::default()
            .direction(Direction::Horizontal)
            .constraints([
                Constraint::Percentage((100 - percent_x) / 2),
                Constraint::Percentage(percent_x),
                Constraint::Percentage((100 - percent_x) / 2),
            ])
            .split(popup_layout[1])[1]
    }

    fn format_timestamp(dt: &Option<chrono::DateTime<chrono::Utc>>) -> Cow<'static, str> {
        match dt {
            Some(t) => Cow::Owned(t.format("%Y-%m-%d %H:%M:%S UTC").to_string()),
            None => Cow::Borrowed("-"),
        }
    }

    fn status_color(status: &str, theme: &Theme) -> Color {
        if status.eq_ignore_ascii_case("PENDING") {
            Color::Yellow
        } else if status.eq_ignore_ascii_case("CLAIMED") {
            Color::Cyan
        } else if status.eq_ignore_ascii_case("RUNNING") {
            Color::Blue
        } else if status.eq_ignore_ascii_case("COMPLETED") {
            theme.success
        } else if status.eq_ignore_ascii_case("FAILED") {
            theme.error
        } else if status.eq_ignore_ascii_case("CANCELLED") {
            theme.muted
        } else if status.eq_ignore_ascii_case("EXPIRED") {
            Color::DarkGray
        } else {
            theme.text
        }
    }

    fn format_json(json_str: &Option<String>) -> Vec<String> {
        match json_str {
            Some(s) if !s.is_empty() => {
                // Try to pretty-print JSON with recursive string parsing
                if let Ok(parsed) = serde_json::from_str::<serde_json::Value>(s) {
                    let expanded = Self::expand_nested_json(parsed);
                    if let Ok(pretty) = serde_json::to_string_pretty(&expanded) {
                        return pretty.lines().map(String::from).collect();
                    }
                }
                // Fallback: show raw string (split long lines)
                s.lines().map(String::from).collect()
            }
            _ => vec!["-".to_string()],
        }
    }

    /// Recursively expand JSON strings that contain escaped JSON
    fn expand_nested_json(value: serde_json::Value) -> serde_json::Value {
        use serde_json::Value;

        match value {
            Value::String(s) => {
                // Try to parse the string as JSON
                if s.starts_with('{') || s.starts_with('[') {
                    if let Ok(inner) = serde_json::from_str::<Value>(&s) {
                        return Self::expand_nested_json(inner);
                    }
                }
                Value::String(s)
            }
            Value::Array(arr) => {
                Value::Array(arr.into_iter().map(Self::expand_nested_json).collect())
            }
            Value::Object(obj) => {
                Value::Object(
                    obj.into_iter()
                        .map(|(k, v)| (k, Self::expand_nested_json(v)))
                        .collect(),
                )
            }
            other => other,
        }
    }

    fn build_detail_lines(&self, theme: &Theme) -> Vec<DetailLine> {
        let mut lines: Vec<DetailLine> = Vec::new();

        // Header section
        lines.push(DetailLine::new(Line::from(vec![
            Span::styled("Task Name:    ", Style::default().fg(theme.muted)),
            Span::styled(self.task.task_name.clone(), Style::default().fg(theme.accent).bold()),
        ])));

        lines.push(DetailLine::new(Line::from(vec![
            Span::styled("Queue:        ", Style::default().fg(theme.muted)),
            Span::styled(self.task.queue_name.clone(), Style::default().fg(theme.text)),
        ])));

        lines.push(DetailLine::new(Line::from(vec![
            Span::styled("Status:       ", Style::default().fg(theme.muted)),
            Span::styled(
                self.task.status.clone(),
                Style::default().fg(Self::status_color(&self.task.status, theme)).bold(),
            ),
        ])));

        if let Some(ref code) = self.task.error_code {
            lines.push(DetailLine::new(Line::from(vec![
                Span::styled("Error Code:   ", Style::default().fg(theme.muted)),
                Span::styled(code.clone(), Style::default().fg(theme.error).bold()),
            ])));
        }

        lines.push(DetailLine::new(Line::from(vec![
            Span::styled("Priority:     ", Style::default().fg(theme.muted)),
            Span::styled(format!("{}", self.task.priority), Style::default().fg(theme.text)),
        ])));

        lines.push(DetailLine::new(Line::from(vec![
            Span::styled("Retries:      ", Style::default().fg(theme.muted)),
            Span::styled(
                format!("{} / {}", self.task.retry_count, self.task.max_retries),
                Style::default().fg(theme.text),
            ),
        ])));

        lines.push(DetailLine::new(Line::from("")));

        // Worker section
        lines.push(DetailLine::new(Line::from(vec![
            Span::styled("Worker Info", Style::default().fg(theme.accent).bold()),
        ])));
        lines.push(DetailLine::new(Line::from(vec![
            Span::styled("  Worker ID:  ", Style::default().fg(theme.muted)),
            Span::styled(
                self.task
                    .claimed_by_worker_id
                    .clone()
                    .unwrap_or_else(|| "-".to_string()),
                Style::default().fg(theme.text),
            ),
        ])));
        lines.push(DetailLine::new(Line::from(vec![
            Span::styled("  Hostname:   ", Style::default().fg(theme.muted)),
            Span::styled(
                self.task
                    .worker_hostname
                    .clone()
                    .unwrap_or_else(|| "-".to_string()),
                Style::default().fg(theme.text),
            ),
        ])));
        lines.push(DetailLine::new(Line::from(vec![
            Span::styled("  PID:        ", Style::default().fg(theme.muted)),
            Span::styled(
                self.task.worker_pid.map(|p| p.to_string()).unwrap_or_else(|| "-".to_string()),
                Style::default().fg(theme.text),
            ),
        ])));
        lines.push(DetailLine::new(Line::from(vec![
            Span::styled("  Process:    ", Style::default().fg(theme.muted)),
            Span::styled(
                self.task.worker_process_name.clone().unwrap_or_else(|| "-".to_string()),
                Style::default().fg(theme.text),
            ),
        ])));

        lines.push(DetailLine::new(Line::from("")));

        // Timestamps section
        lines.push(DetailLine::new(Line::from(vec![
            Span::styled("Timeline", Style::default().fg(theme.accent).bold()),
        ])));
        lines.push(DetailLine::new(Line::from(vec![
            Span::styled("  Created:    ", Style::default().fg(theme.muted)),
            Span::styled(
                self.task.created_at.format("%Y-%m-%d %H:%M:%S UTC").to_string(),
                Style::default().fg(theme.text),
            ),
        ])));
        lines.push(DetailLine::new(Line::from(vec![
            Span::styled("  Sent:       ", Style::default().fg(theme.muted)),
            Span::styled(Self::format_timestamp(&self.task.sent_at), Style::default().fg(theme.text)),
        ])));
        lines.push(DetailLine::new(Line::from(vec![
            Span::styled("  Enqueued:   ", Style::default().fg(theme.muted)),
            Span::styled(self.task.enqueued_at.format("%Y-%m-%d %H:%M:%S UTC").to_string(), Style::default().fg(theme.text)),
        ])));
        lines.push(DetailLine::new(Line::from(vec![
            Span::styled("  Claimed:    ", Style::default().fg(theme.muted)),
            Span::styled(Self::format_timestamp(&self.task.claimed_at), Style::default().fg(theme.text)),
        ])));
        lines.push(DetailLine::new(Line::from(vec![
            Span::styled("  Started:    ", Style::default().fg(theme.muted)),
            Span::styled(Self::format_timestamp(&self.task.started_at), Style::default().fg(theme.text)),
        ])));
        lines.push(DetailLine::new(Line::from(vec![
            Span::styled("  Completed:  ", Style::default().fg(theme.muted)),
            Span::styled(Self::format_timestamp(&self.task.completed_at), Style::default().fg(theme.text)),
        ])));
        lines.push(DetailLine::new(Line::from(vec![
            Span::styled("  Failed:     ", Style::default().fg(theme.muted)),
            Span::styled(Self::format_timestamp(&self.task.failed_at), Style::default().fg(theme.text)),
        ])));

        lines.push(DetailLine::new(Line::from("")));

        // Attempts section
        if !self.task.attempts.is_empty() {
            lines.push(DetailLine::new(Line::from(vec![
                Span::styled("Attempts", Style::default().fg(theme.accent).bold()),
            ])));
            for (i, attempt) in self.task.attempts.iter().enumerate() {
                let retry_label = if attempt.will_retry { "yes" } else { "no" };
                let outcome_color = match attempt.outcome.as_str() {
                    "COMPLETED" => theme.success,
                    "FAILED" => theme.error,
                    "WORKER_FAILURE" => theme.error,
                    _ => theme.text,
                };
                lines.push(DetailLine::new(Line::from(vec![
                    Span::styled(
                        format!("  Attempt {} | ", attempt.attempt),
                        Style::default().fg(theme.muted),
                    ),
                    Span::styled(
                        attempt.outcome.clone(),
                        Style::default().fg(outcome_color).bold(),
                    ),
                    Span::styled(
                        format!(" | retry={}", retry_label),
                        Style::default().fg(theme.muted),
                    ),
                ])));
                lines.push(DetailLine::new(Line::from(vec![
                    Span::styled("    Started:  ", Style::default().fg(theme.muted)),
                    Span::styled(
                        attempt.started_at.format("%Y-%m-%d %H:%M:%S UTC").to_string(),
                        Style::default().fg(theme.text),
                    ),
                ])));
                lines.push(DetailLine::new(Line::from(vec![
                    Span::styled("    Finished: ", Style::default().fg(theme.muted)),
                    Span::styled(
                        attempt.finished_at.format("%Y-%m-%d %H:%M:%S UTC").to_string(),
                        Style::default().fg(theme.text),
                    ),
                ])));
                if let Some(ref code) = attempt.error_code {
                    lines.push(DetailLine::new(Line::from(vec![
                        Span::styled("    Error Code:    ", Style::default().fg(theme.muted)),
                        Span::styled(code.clone(), Style::default().fg(theme.error)),
                    ])));
                }
                if let Some(ref msg) = attempt.error_message {
                    lines.push(DetailLine::new(Line::from(vec![
                        Span::styled("    Error Message: ", Style::default().fg(theme.muted)),
                        Span::styled(msg.clone(), Style::default().fg(theme.error)),
                    ])));
                }
                if let Some(ref reason) = attempt.failed_reason {
                    lines.push(DetailLine::new(Line::from(vec![
                        Span::styled("    Failed Reason: ", Style::default().fg(theme.muted)),
                        Span::styled(reason.clone(), Style::default().fg(theme.error)),
                    ])));
                }
                // Worker info line when any worker field is present
                let has_worker = attempt.worker_id.is_some()
                    || attempt.worker_hostname.is_some()
                    || attempt.worker_pid.is_some()
                    || attempt.worker_process_name.is_some();
                if has_worker {
                    let parts: Vec<String> = [
                        attempt.worker_id.clone(),
                        attempt.worker_hostname.clone(),
                        attempt.worker_pid.map(|p| p.to_string()),
                        attempt.worker_process_name.clone(),
                    ]
                    .into_iter()
                    .flatten()
                    .collect();
                    lines.push(DetailLine::new(Line::from(vec![
                        Span::styled("    Worker: ", Style::default().fg(theme.muted)),
                        Span::styled(parts.join(" | "), Style::default().fg(theme.text)),
                    ])));
                }
                // Blank line between attempts (except after the last one)
                if i < self.task.attempts.len() - 1 {
                    lines.push(DetailLine::new(Line::from("")));
                }
            }
            lines.push(DetailLine::new(Line::from("")));
        }

        // Arguments section
        lines.push(DetailLine::new(Line::from(vec![
            Span::styled("Arguments", Style::default().fg(theme.accent).bold()),
        ])));
        lines.push(DetailLine::new(Line::from(vec![
            Span::styled("  args:   ", Style::default().fg(theme.muted)),
        ])));
        for arg_line in Self::format_json(&self.task.args) {
            lines.push(DetailLine::new(Line::from(vec![
                Span::styled(format!("    {}", arg_line), Style::default().fg(theme.text)),
            ])));
        }
        lines.push(DetailLine::new(Line::from(vec![
            Span::styled("  kwargs: ", Style::default().fg(theme.muted)),
        ])));
        for kwarg_line in Self::format_json(&self.task.kwargs) {
            lines.push(DetailLine::new(Line::from(vec![
                Span::styled(format!("    {}", kwarg_line), Style::default().fg(theme.text)),
            ])));
        }

        lines.push(DetailLine::new(Line::from("")));

        // Result/Error section
        if self.task.status.to_uppercase() == "COMPLETED" {
            lines.push(DetailLine::new(Line::from(vec![
                Span::styled("Result", Style::default().fg(theme.success).bold()),
            ])));
            for result_line in Self::format_json(&self.task.result) {
                lines.push(DetailLine::new(Line::from(vec![
                    Span::styled(format!("  {}", result_line), Style::default().fg(theme.text)),
                ])));
            }
        } else if self.task.status.to_uppercase() == "FAILED" {
            lines.push(DetailLine::new(Line::from(vec![
                Span::styled("Error", Style::default().fg(theme.error).bold()),
            ])));

            let has_failed_reason = self.task.failed_reason.as_ref().is_some_and(|s| !s.is_empty());
            let has_result = self.task.result.as_ref().is_some_and(|s| !s.is_empty());

            if has_failed_reason {
                lines.push(DetailLine::new(Line::from(vec![
                    Span::styled("  Reason:", Style::default().fg(theme.error).bold()),
                ])));
                for error_line in Self::format_json(&self.task.failed_reason) {
                    lines.push(DetailLine::new(Line::from(vec![
                        Span::styled(format!("    {}", error_line), Style::default().fg(theme.error)),
                    ])));
                }
            }

            if has_result {
                if has_failed_reason {
                    lines.push(DetailLine::new(Line::from("")));
                }
                lines.push(DetailLine::new(Line::from(vec![
                    Span::styled("  Result:", Style::default().fg(theme.error).bold()),
                ])));
                for result_line in Self::format_json(&self.task.result) {
                    lines.push(DetailLine::new(Line::from(vec![
                        Span::styled(format!("    {}", result_line), Style::default().fg(theme.error)),
                    ])));
                }
            }

            if !has_failed_reason && !has_result {
                lines.push(DetailLine::new(Line::from(vec![
                    Span::styled("  -", Style::default().fg(theme.error)),
                ])));
            }
        }

        lines
    }

    pub fn build_search_lines(&self, theme: &Theme) -> Vec<SearchableLine> {
        self.build_detail_lines(theme)
            .into_iter()
            .map(|line| SearchableLine {
                display: line.display_text,
                search_text: line.search_text,
            })
            .collect()
    }

    pub fn build_display_lines(&self, theme: &Theme) -> Vec<Line<'static>> {
        self.build_detail_lines(theme)
            .into_iter()
            .map(|line| line.display)
            .collect()
    }
}

impl<'a> Component for TaskDetailPanel<'a> {
    fn update(&mut self, _action: Action) -> Result<Option<Action>> {
        Ok(None)
    }

    fn draw(&mut self, frame: &mut Frame, area: Rect, theme: &Theme) -> Result<()> {
        let popup_area = Self::centered_rect(80, 85, area);

        // Clear the background
        frame.render_widget(Clear, popup_area);

        let title = format!(" Task: {} - Esc: close | ↑↓/PgUp/PgDn/Home/End: scroll | []: prev/next | y: copy ", self.task.id);
        let block = Block::default()
            .title(title)
            .borders(Borders::ALL)
            .border_style(Style::default().fg(theme.accent))
            .border_type(BorderType::Rounded)
            .padding(Padding::uniform(1))
            .style(Style::default().bg(theme.surface));

        let inner = block.inner(popup_area);
        frame.render_widget(block, popup_area);

        let owned_lines;
        let source_lines: &[Line<'static>] = if let Some(cached) = self.cached_lines {
            cached
        } else {
            owned_lines = self.build_display_lines(theme);
            &owned_lines
        };

        // Calculate total content height for scrollbar
        let content_height = source_lines.len() as u16;
        let visible_height = inner.height;
        self.content_height = content_height;
        self.visible_height = visible_height;

        // Apply scroll offset (clamp to valid range).
        // Without Wrap, logical lines == visual lines so manual slicing is correct.
        let scroll = self.scroll_offset.min(content_height.saturating_sub(visible_height));
        self.effective_scroll = scroll;
        let start = scroll as usize;
        let end = (start + visible_height as usize).min(source_lines.len());
        let mut visible_lines: Vec<Line> = source_lines[start..end].iter().cloned().collect();

        // Add search pointer only for visible highlighted lines.
        if let Some(highlight) = &self.highlight {
            for (offset, line) in visible_lines.iter_mut().enumerate() {
                if highlight.matches_line(start + offset) {
                    line.spans.push(Span::styled(
                        format!(" {}", highlight.pointer()),
                        Style::default().fg(theme.accent),
                    ));
                }
            }
        }

        let paragraph = Paragraph::new(visible_lines)
            .style(Style::default().bg(theme.background).fg(theme.text))
            .scroll((0, 0));

        frame.render_widget(paragraph, inner);

        // Render scrollbar if content overflows
        if content_height > visible_height {
            let scrollbar = Scrollbar::new(ScrollbarOrientation::VerticalRight)
                .begin_symbol(Some("▲"))
                .end_symbol(Some("▼"))
                .track_symbol(Some("│"))
                .thumb_symbol("█");

            let scrollbar_track = inner.inner(Margin { vertical: 1, horizontal: 0 });
            self.scrollbar_area = Some(scrollbar_track);

            let mut scrollbar_state = ScrollbarState::new(content_height as usize)
                .position(scroll as usize)
                .viewport_content_length(visible_height as usize);

            frame.render_stateful_widget(
                scrollbar,
                scrollbar_track,
                &mut scrollbar_state,
            );
        } else {
            self.scrollbar_area = None;
        }

        Ok(())
    }
}
