use cosimo::{CardMemoryState, CardSchedule, Datum, DatumKey, ReviewEvent, schedule_datum_for_key};

use crate::localization::{CardDetailsLabel as Detail, Localizer};
use crate::time_format::{
    format_relative_duration_complete, format_relative_duration_rounded, format_stability_days,
    format_unix_timestamp_utc,
};

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) struct CardDetailsState {
    pub(crate) is_suspended: bool,
    pub(crate) is_flagged: bool,
    pub(crate) is_deferred: bool,
    pub(crate) is_non_reversible: bool,
    pub(crate) is_reversible: bool,
    pub(crate) is_reverse: bool,
}

pub(crate) fn card_details_text(
    localizer: Localizer,
    datum_key: &DatumKey,
    datum: &Datum,
    reviews: &[ReviewEvent],
    schedule: Option<&CardSchedule>,
    card_state: CardDetailsState,
    now_unix_seconds: i64,
) -> String {
    let scheduled = schedule_datum_for_key(datum_key.clone(), schedule, now_unix_seconds);
    let not_available = localizer.card_details_not_available();
    let none = localizer.card_details_none();
    let card_id = display_card_id(datum_key, datum)
        .map(|card_id| card_id.to_string())
        .unwrap_or_else(|| not_available.to_string());
    let last_review = reviews.last();
    let last_reviewed_at = last_review
        .map(|event| {
            format_datetime_with_delta(
                localizer,
                event.reviewed_at_unix_seconds(),
                now_unix_seconds,
            )
        })
        .unwrap_or_else(|| none.to_string());
    let last_prompt_guess = last_review
        .map(|event| localizer.prompt_guess_button_label(event.review().prompt_guess()))
        .unwrap_or(none);
    let last_recall_feedback = last_review
        .map(|event| localizer.recall_feedback_button_label(event.review().recall_feedback()))
        .unwrap_or(none);
    let next_review = format_next_review(localizer, schedule, now_unix_seconds);
    let due_now = if scheduled.is_due() {
        localizer.card_details_yes()
    } else {
        localizer.card_details_no()
    };

    let mut details = String::new();
    push_detail_line(
        &mut details,
        localizer.card_details_label(Detail::Prompt),
        datum.prompt(),
    );
    push_detail_line(
        &mut details,
        localizer.card_details_label(Detail::Response),
        datum.response(),
    );
    push_detail_line(
        &mut details,
        localizer.card_details_label(Detail::CardId),
        &card_id,
    );
    push_detail_line(
        &mut details,
        localizer.card_details_label(Detail::DatumKey),
        datum_key.as_str(),
    );
    push_detail_line(
        &mut details,
        localizer.card_details_label(Detail::State),
        &format_card_state(localizer, card_state),
    );
    details.push('\n');
    push_detail_line(
        &mut details,
        localizer.card_details_label(Detail::NumberOfReviews),
        &reviews.len().to_string(),
    );
    push_detail_line(
        &mut details,
        localizer.card_details_label(Detail::LastReview),
        &last_reviewed_at,
    );
    push_detail_line(
        &mut details,
        localizer.card_details_label(Detail::LastConfidenceRating),
        last_prompt_guess,
    );
    push_detail_line(
        &mut details,
        localizer.card_details_label(Detail::LastRecallRating),
        last_recall_feedback,
    );
    details.push('\n');
    push_detail_line(
        &mut details,
        localizer.card_details_label(Detail::DueNow),
        due_now,
    );
    push_detail_line(
        &mut details,
        localizer.card_details_label(Detail::NextScheduledReview),
        &next_review,
    );
    push_detail_line(
        &mut details,
        localizer.card_details_label(Detail::CurrentInterval),
        &format_relative_duration_rounded(localizer, scheduled.interval_seconds()),
    );

    match schedule {
        Some(schedule) => {
            push_detail_line(
                &mut details,
                localizer.card_details_label(Detail::MemoryState),
                localizer.card_memory_state_label(schedule.memory_state()),
            );
            push_detail_line(
                &mut details,
                localizer.card_details_label(Detail::Stability),
                &format_stability_days(localizer, schedule.stability_days()),
            );
            push_detail_line(
                &mut details,
                localizer.card_details_label(Detail::Difficulty),
                &format!("{:.2}", schedule.difficulty()),
            );
            push_detail_line(
                &mut details,
                localizer.card_details_label(Detail::ElapsedDaysAtLastReview),
                &schedule.elapsed_days().to_string(),
            );
            push_detail_line(
                &mut details,
                localizer.card_details_label(Detail::ScheduledDays),
                &schedule.scheduled_days().to_string(),
            );
            push_detail_line(
                &mut details,
                localizer.card_details_label(Detail::Repetitions),
                &schedule.repetitions().to_string(),
            );
            push_detail_line(
                &mut details,
                localizer.card_details_label(Detail::Lapses),
                &schedule.lapses().to_string(),
            );
            push_detail_line(
                &mut details,
                localizer.card_details_label(Detail::ScheduleLastReviewed),
                &schedule
                    .last_reviewed_at_unix_seconds()
                    .map(|timestamp| {
                        format_datetime_with_delta(localizer, timestamp, now_unix_seconds)
                    })
                    .unwrap_or_else(|| none.to_string()),
            );
            push_detail_line(
                &mut details,
                localizer.card_details_label(Detail::ScheduleLastRecallRating),
                schedule
                    .last_recall_feedback()
                    .map(|feedback| localizer.recall_feedback_button_label(feedback))
                    .unwrap_or(none),
            );
        }
        None => {
            push_detail_line(
                &mut details,
                localizer.card_details_label(Detail::MemoryState),
                localizer.card_memory_state_label(CardMemoryState::New),
            );
            push_detail_line(
                &mut details,
                localizer.card_details_label(Detail::StoredSchedule),
                none,
            );
        }
    }

    if !reviews.is_empty() {
        details.push('\n');
        details.push_str(localizer.card_details_label(Detail::ReviewHistory));
        details.push('\n');
        for event in reviews {
            details.push_str(&format!(
                "{}. {}. {}: {}. {}: {}.\n",
                event.id(),
                format_unix_timestamp_utc(event.reviewed_at_unix_seconds()),
                localizer.card_details_label(Detail::Confidence),
                localizer.prompt_guess_button_label(event.review().prompt_guess()),
                localizer.card_details_label(Detail::Recall),
                localizer.recall_feedback_button_label(event.review().recall_feedback())
            ));
        }
    }

    details
}

fn format_card_state(localizer: Localizer, card_state: CardDetailsState) -> String {
    let mut states = Vec::new();
    if card_state.is_reverse {
        states.push(localizer.card_details_state_reverse());
    } else if card_state.is_reversible {
        states.push(localizer.card_details_state_has_reverse());
    }
    if card_state.is_suspended {
        states.push(localizer.card_details_state_suspended());
    }
    if card_state.is_flagged {
        states.push(localizer.card_details_state_flagged());
    }
    if card_state.is_deferred {
        states.push(localizer.card_details_state_deferred());
    }
    if card_state.is_non_reversible {
        states.push(localizer.card_details_state_non_reversible());
    }

    if states.is_empty() {
        localizer.card_details_state_normal().to_string()
    } else {
        states.join(", ")
    }
}

fn display_card_id(datum_key: &DatumKey, datum: &Datum) -> Option<u64> {
    datum.card_id().or_else(|| {
        datum_key
            .parent_key_for_reverse()
            .and_then(|parent_key| parent_key.card_id())
    })
}

fn format_next_review(
    localizer: Localizer,
    schedule: Option<&CardSchedule>,
    now_unix_seconds: i64,
) -> String {
    let Some(schedule) = schedule else {
        return localizer.card_details_new_card_due_now().to_string();
    };
    let due_at = schedule.due_at_unix_seconds();
    if due_at == now_unix_seconds {
        format_unix_timestamp_utc(due_at)
    } else {
        format_datetime_with_delta(localizer, due_at, now_unix_seconds)
    }
}

fn format_datetime_with_delta(
    localizer: Localizer,
    timestamp_unix_seconds: i64,
    now_unix_seconds: i64,
) -> String {
    let timestamp = format_unix_timestamp_utc(timestamp_unix_seconds);
    if timestamp_unix_seconds > now_unix_seconds {
        let duration =
            format_relative_duration_complete(localizer, timestamp_unix_seconds - now_unix_seconds);
        localizer.card_details_future_datetime(&duration, &timestamp)
    } else {
        let duration =
            format_relative_duration_complete(localizer, now_unix_seconds - timestamp_unix_seconds);
        localizer.card_details_past_datetime(&duration, &timestamp)
    }
}

fn push_detail_line(details: &mut String, label: &str, value: &str) {
    details.push_str(label);
    details.push_str(": ");
    details.push_str(value);
    details.push('\n');
}
