fn format_date(ms: i64) -> String {
    render::format_date(ms)
}

fn wrap_text(text: &str, width: usize, indent: &str) -> String {
    render::wrap_indent(text, width, indent)
}

pub fn print_show_issue(issue: &IssueDetail) {
    // Title line
    println!(
        "   Issue #{}: {}",
        issue.iid,
        Theme::bold().render(&issue.title),
    );

    // Details section
    println!("{}", render::section_divider("Details"));

    println!(
        "     Ref        {}",
        Theme::muted().render(&issue.references_full)
    );
    println!(
        "     Project    {}",
        Theme::info().render(&issue.project_path)
    );

    let (icon, state_style) = if issue.state == "opened" {
        (Icons::issue_opened(), Theme::success())
    } else {
        (Icons::issue_closed(), Theme::dim())
    };
    println!(
        "     State      {}",
        state_style.render(&format!("{icon} {}", issue.state))
    );

    if let Some(status) = &issue.status_name {
        println!(
            "     Status     {}",
            render::style_with_hex(status, issue.status_color.as_deref())
        );
    }

    if issue.confidential {
        println!("     {}", Theme::error().bold().render("CONFIDENTIAL"));
    }

    println!("     Author     @{}", issue.author_username);

    if !issue.assignees.is_empty() {
        let label = if issue.assignees.len() > 1 {
            "Assignees"
        } else {
            "Assignee"
        };
        println!(
            "     {}{} {}",
            label,
            " ".repeat(12 - label.len()),
            issue
                .assignees
                .iter()
                .map(|a| format!("@{a}"))
                .collect::<Vec<_>>()
                .join(", ")
        );
    }

    println!(
        "     Created    {}  ({})",
        format_date(issue.created_at),
        render::format_relative_time_compact(issue.created_at),
    );
    println!(
        "     Updated    {}  ({})",
        format_date(issue.updated_at),
        render::format_relative_time_compact(issue.updated_at),
    );

    if let Some(closed_at) = &issue.closed_at {
        println!("     Closed     {closed_at}");
    }

    if let Some(due) = &issue.due_date {
        println!("     Due        {due}");
    }

    if let Some(ms) = &issue.milestone {
        println!("     Milestone  {ms}");
    }

    if !issue.labels.is_empty() {
        println!(
            "     Labels     {}",
            render::format_labels_bare(&issue.labels, issue.labels.len())
        );
    }

    if let Some(url) = &issue.web_url {
        println!("     URL        {}", Theme::muted().render(url));
    }

    // Development section
    if !issue.closing_merge_requests.is_empty() {
        println!("{}", render::section_divider("Development"));
        for mr in &issue.closing_merge_requests {
            let (mr_icon, mr_style) = match mr.state.as_str() {
                "merged" => (Icons::mr_merged(), Theme::accent()),
                "opened" => (Icons::mr_opened(), Theme::success()),
                "closed" => (Icons::mr_closed(), Theme::error()),
                _ => (Icons::mr_opened(), Theme::dim()),
            };
            println!(
                "     {} !{}  {}   {}",
                mr_style.render(mr_icon),
                mr.iid,
                mr.title,
                mr_style.render(&mr.state),
            );
        }
    }

    // Description section
    println!("{}", render::section_divider("Description"));
    if let Some(desc) = &issue.description {
        let wrapped = wrap_text(desc, 72, "     ");
        println!("     {wrapped}");
    } else {
        println!("     {}", Theme::muted().render("(no description)"));
    }

    // Discussions section
    let user_discussions: Vec<&DiscussionDetail> = issue
        .discussions
        .iter()
        .filter(|d| d.notes.iter().any(|n| !n.is_system))
        .collect();

    if user_discussions.is_empty() {
        println!("\n     {}", Theme::muted().render("No discussions"));
    } else {
        println!(
            "{}",
            render::section_divider(&format!("Discussions ({})", user_discussions.len()))
        );

        for discussion in user_discussions {
            let user_notes: Vec<&NoteDetail> =
                discussion.notes.iter().filter(|n| !n.is_system).collect();

            if let Some(first_note) = user_notes.first() {
                println!(
                    "     {}  {}",
                    Theme::info().render(&format!("@{}", first_note.author_username)),
                    format_date(first_note.created_at),
                );
                let wrapped = wrap_text(&first_note.body, 68, "     ");
                println!("     {wrapped}");
                println!();

                for reply in user_notes.iter().skip(1) {
                    println!(
                        "       {}  {}",
                        Theme::info().render(&format!("@{}", reply.author_username)),
                        format_date(reply.created_at),
                    );
                    let wrapped = wrap_text(&reply.body, 66, "       ");
                    println!("       {wrapped}");
                    println!();
                }
            }
        }
    }
}

pub fn print_show_mr(mr: &MrDetail) {
    // Title line
    let draft_prefix = if mr.draft {
        format!("{} ", Icons::mr_draft())
    } else {
        String::new()
    };
    println!(
        "   MR !{}: {}{}",
        mr.iid,
        draft_prefix,
        Theme::bold().render(&mr.title),
    );

    // Details section
    println!("{}", render::section_divider("Details"));

    println!("     Project    {}", Theme::info().render(&mr.project_path));

    let (icon, state_style) = match mr.state.as_str() {
        "opened" => (Icons::mr_opened(), Theme::success()),
        "merged" => (Icons::mr_merged(), Theme::accent()),
        "closed" => (Icons::mr_closed(), Theme::error()),
        _ => (Icons::mr_opened(), Theme::dim()),
    };
    println!(
        "     State      {}",
        state_style.render(&format!("{icon} {}", mr.state))
    );

    println!(
        "     Branches   {} -> {}",
        Theme::info().render(&mr.source_branch),
        Theme::warning().render(&mr.target_branch)
    );

    println!("     Author     @{}", mr.author_username);

    if !mr.assignees.is_empty() {
        println!(
            "     Assignees  {}",
            mr.assignees
                .iter()
                .map(|a| format!("@{a}"))
                .collect::<Vec<_>>()
                .join(", ")
        );
    }

    if !mr.reviewers.is_empty() {
        println!(
            "     Reviewers  {}",
            mr.reviewers
                .iter()
                .map(|r| format!("@{r}"))
                .collect::<Vec<_>>()
                .join(", ")
        );
    }

    println!(
        "     Created    {}  ({})",
        format_date(mr.created_at),
        render::format_relative_time_compact(mr.created_at),
    );
    println!(
        "     Updated    {}  ({})",
        format_date(mr.updated_at),
        render::format_relative_time_compact(mr.updated_at),
    );

    if let Some(merged_at) = mr.merged_at {
        println!(
            "     Merged     {}  ({})",
            format_date(merged_at),
            render::format_relative_time_compact(merged_at),
        );
    }

    if let Some(closed_at) = mr.closed_at {
        println!(
            "     Closed     {}  ({})",
            format_date(closed_at),
            render::format_relative_time_compact(closed_at),
        );
    }

    if !mr.labels.is_empty() {
        println!(
            "     Labels     {}",
            render::format_labels_bare(&mr.labels, mr.labels.len())
        );
    }

    if let Some(url) = &mr.web_url {
        println!("     URL        {}", Theme::muted().render(url));
    }

    // Description section
    println!("{}", render::section_divider("Description"));
    if let Some(desc) = &mr.description {
        let wrapped = wrap_text(desc, 72, "     ");
        println!("     {wrapped}");
    } else {
        println!("     {}", Theme::muted().render("(no description)"));
    }

    // Discussions section
    let user_discussions: Vec<&MrDiscussionDetail> = mr
        .discussions
        .iter()
        .filter(|d| d.notes.iter().any(|n| !n.is_system))
        .collect();

    if user_discussions.is_empty() {
        println!("\n     {}", Theme::muted().render("No discussions"));
    } else {
        println!(
            "{}",
            render::section_divider(&format!("Discussions ({})", user_discussions.len()))
        );

        for discussion in user_discussions {
            let user_notes: Vec<&MrNoteDetail> =
                discussion.notes.iter().filter(|n| !n.is_system).collect();

            if let Some(first_note) = user_notes.first() {
                if let Some(pos) = &first_note.position {
                    print_diff_position(pos);
                }

                println!(
                    "     {}  {}",
                    Theme::info().render(&format!("@{}", first_note.author_username)),
                    format_date(first_note.created_at),
                );
                let wrapped = wrap_text(&first_note.body, 68, "     ");
                println!("     {wrapped}");
                println!();

                for reply in user_notes.iter().skip(1) {
                    println!(
                        "       {}  {}",
                        Theme::info().render(&format!("@{}", reply.author_username)),
                        format_date(reply.created_at),
                    );
                    let wrapped = wrap_text(&reply.body, 66, "       ");
                    println!("       {wrapped}");
                    println!();
                }
            }
        }
    }
}

fn print_diff_position(pos: &DiffNotePosition) {
    let file = pos.new_path.as_ref().or(pos.old_path.as_ref());

    if let Some(file_path) = file {
        let line_str = match (pos.old_line, pos.new_line) {
            (Some(old), Some(new)) if old == new => format!(":{}", new),
            (Some(old), Some(new)) => format!(":{}→{}", old, new),
            (None, Some(new)) => format!(":+{}", new),
            (Some(old), None) => format!(":-{}", old),
            (None, None) => String::new(),
        };

        println!(
            "  {} {}{}",
            Theme::dim().render("\u{1f4cd}"),
            Theme::warning().render(file_path),
            Theme::dim().render(&line_str)
        );
    }
}

#[derive(Serialize)]
pub struct IssueDetailJson {
    pub id: i64,
    pub iid: i64,
    pub title: String,
    pub description: Option<String>,
    pub state: String,
    pub author_username: String,
    pub created_at: String,
    pub updated_at: String,
    pub closed_at: Option<String>,
    pub confidential: bool,
    pub web_url: Option<String>,
    pub project_path: String,
    pub references_full: String,
    pub labels: Vec<String>,
    pub assignees: Vec<String>,
    pub due_date: Option<String>,
    pub milestone: Option<String>,
    pub user_notes_count: i64,
    pub merge_requests_count: usize,
    pub closing_merge_requests: Vec<ClosingMrRefJson>,
    pub discussions: Vec<DiscussionDetailJson>,
    pub status_name: Option<String>,
    #[serde(skip_serializing)]
    pub status_category: Option<String>,
    pub status_color: Option<String>,
    pub status_icon_name: Option<String>,
    pub status_synced_at: Option<String>,
}

#[derive(Serialize)]
pub struct ClosingMrRefJson {
    pub iid: i64,
    pub title: String,
    pub state: String,
    pub web_url: Option<String>,
}

#[derive(Serialize)]
pub struct DiscussionDetailJson {
    pub notes: Vec<NoteDetailJson>,
    pub individual_note: bool,
}

#[derive(Serialize)]
pub struct NoteDetailJson {
    pub gitlab_id: i64,
    pub author_username: String,
    pub body: String,
    pub created_at: String,
    pub is_system: bool,
}

impl From<&IssueDetail> for IssueDetailJson {
    fn from(issue: &IssueDetail) -> Self {
        Self {
            id: issue.id,
            iid: issue.iid,
            title: issue.title.clone(),
            description: issue.description.clone(),
            state: issue.state.clone(),
            author_username: issue.author_username.clone(),
            created_at: ms_to_iso(issue.created_at),
            updated_at: ms_to_iso(issue.updated_at),
            closed_at: issue.closed_at.clone(),
            confidential: issue.confidential,
            web_url: issue.web_url.clone(),
            project_path: issue.project_path.clone(),
            references_full: issue.references_full.clone(),
            labels: issue.labels.clone(),
            assignees: issue.assignees.clone(),
            due_date: issue.due_date.clone(),
            milestone: issue.milestone.clone(),
            user_notes_count: issue.user_notes_count,
            merge_requests_count: issue.merge_requests_count,
            closing_merge_requests: issue
                .closing_merge_requests
                .iter()
                .map(|mr| ClosingMrRefJson {
                    iid: mr.iid,
                    title: mr.title.clone(),
                    state: mr.state.clone(),
                    web_url: mr.web_url.clone(),
                })
                .collect(),
            discussions: issue.discussions.iter().map(|d| d.into()).collect(),
            status_name: issue.status_name.clone(),
            status_category: issue.status_category.clone(),
            status_color: issue.status_color.clone(),
            status_icon_name: issue.status_icon_name.clone(),
            status_synced_at: issue.status_synced_at.map(ms_to_iso),
        }
    }
}

impl From<&DiscussionDetail> for DiscussionDetailJson {
    fn from(disc: &DiscussionDetail) -> Self {
        Self {
            notes: disc.notes.iter().map(|n| n.into()).collect(),
            individual_note: disc.individual_note,
        }
    }
}

impl From<&NoteDetail> for NoteDetailJson {
    fn from(note: &NoteDetail) -> Self {
        Self {
            gitlab_id: note.gitlab_id,
            author_username: note.author_username.clone(),
            body: note.body.clone(),
            created_at: ms_to_iso(note.created_at),
            is_system: note.is_system,
        }
    }
}

#[derive(Serialize)]
pub struct MrDetailJson {
    pub id: i64,
    pub iid: i64,
    pub title: String,
    pub description: Option<String>,
    pub state: String,
    pub draft: bool,
    pub author_username: String,
    pub source_branch: String,
    pub target_branch: String,
    pub created_at: String,
    pub updated_at: String,
    pub merged_at: Option<String>,
    pub closed_at: Option<String>,
    pub web_url: Option<String>,
    pub project_path: String,
    pub labels: Vec<String>,
    pub assignees: Vec<String>,
    pub reviewers: Vec<String>,
    pub discussions: Vec<MrDiscussionDetailJson>,
}

#[derive(Serialize)]
pub struct MrDiscussionDetailJson {
    pub notes: Vec<MrNoteDetailJson>,
    pub individual_note: bool,
}

#[derive(Serialize)]
pub struct MrNoteDetailJson {
    pub gitlab_id: i64,
    pub author_username: String,
    pub body: String,
    pub created_at: String,
    pub is_system: bool,
    pub position: Option<DiffNotePosition>,
}

impl From<&MrDetail> for MrDetailJson {
    fn from(mr: &MrDetail) -> Self {
        Self {
            id: mr.id,
            iid: mr.iid,
            title: mr.title.clone(),
            description: mr.description.clone(),
            state: mr.state.clone(),
            draft: mr.draft,
            author_username: mr.author_username.clone(),
            source_branch: mr.source_branch.clone(),
            target_branch: mr.target_branch.clone(),
            created_at: ms_to_iso(mr.created_at),
            updated_at: ms_to_iso(mr.updated_at),
            merged_at: mr.merged_at.map(ms_to_iso),
            closed_at: mr.closed_at.map(ms_to_iso),
            web_url: mr.web_url.clone(),
            project_path: mr.project_path.clone(),
            labels: mr.labels.clone(),
            assignees: mr.assignees.clone(),
            reviewers: mr.reviewers.clone(),
            discussions: mr.discussions.iter().map(|d| d.into()).collect(),
        }
    }
}

impl From<&MrDiscussionDetail> for MrDiscussionDetailJson {
    fn from(disc: &MrDiscussionDetail) -> Self {
        Self {
            notes: disc.notes.iter().map(|n| n.into()).collect(),
            individual_note: disc.individual_note,
        }
    }
}

impl From<&MrNoteDetail> for MrNoteDetailJson {
    fn from(note: &MrNoteDetail) -> Self {
        Self {
            gitlab_id: note.gitlab_id,
            author_username: note.author_username.clone(),
            body: note.body.clone(),
            created_at: ms_to_iso(note.created_at),
            is_system: note.is_system,
            position: note.position.clone(),
        }
    }
}

pub fn print_show_issue_json(issue: &IssueDetail, elapsed_ms: u64) {
    let json_result = IssueDetailJson::from(issue);
    let meta = RobotMeta::new(elapsed_ms);
    let output = serde_json::json!({
        "ok": true,
        "data": json_result,
        "meta": meta,
    });
    match serde_json::to_string(&output) {
        Ok(json) => println!("{json}"),
        Err(e) => eprintln!("Error serializing to JSON: {e}"),
    }
}

pub fn print_show_mr_json(mr: &MrDetail, elapsed_ms: u64) {
    let json_result = MrDetailJson::from(mr);
    let meta = RobotMeta::new(elapsed_ms);
    let output = serde_json::json!({
        "ok": true,
        "data": json_result,
        "meta": meta,
    });
    match serde_json::to_string(&output) {
        Ok(json) => println!("{json}"),
        Err(e) => eprintln!("Error serializing to JSON: {e}"),
    }
}
