use crate::app_config::AppConfig;
use crate::localization::{DeckPropertiesLabel as Detail, Localizer};
use crate::sync_identity::display_owner_identity;
use cosimo::{
    DeckMetadata, DeckVisibility, LocalDeckSyncState, SyncQuotaStatus, SyncRemoteDeckStatus,
};

pub(crate) fn sync_status_text(
    localizer: Localizer,
    metadata: &DeckMetadata,
    config: &AppConfig,
) -> String {
    sync_status_text_with_remote(localizer, metadata, config, None)
}

#[derive(Debug, Clone)]
pub(crate) enum RemoteSyncStatusOverride {
    NotFound,
    AuthenticationFailed,
    ReadError(String),
    Found(Box<SyncRemoteDeckStatus>),
    FoundWithOwnerAuthenticationFailed {
        status: Box<SyncRemoteDeckStatus>,
    },
    FoundWithOwnerReadError {
        status: Box<SyncRemoteDeckStatus>,
        owner_error: String,
    },
}

#[derive(Debug, Clone)]
pub(crate) enum RemoteSyncQuotaOverride {
    AuthenticationFailed,
    ReadError(String),
    Found(SyncQuotaStatus),
}

pub(crate) fn sync_status_text_with_remote(
    localizer: Localizer,
    metadata: &DeckMetadata,
    config: &AppConfig,
    remote_status_override: Option<RemoteSyncStatusOverride>,
) -> String {
    sync_status_text_with_remote_and_quota(
        localizer,
        metadata,
        config,
        remote_status_override,
        None,
    )
}

pub(crate) fn sync_status_text_with_remote_and_quota(
    localizer: Localizer,
    metadata: &DeckMetadata,
    config: &AppConfig,
    remote_status_override: Option<RemoteSyncStatusOverride>,
    quota_override: Option<RemoteSyncQuotaOverride>,
) -> String {
    let none = localizer.card_details_none();
    let deck_uuid = metadata.deck_uuid();
    let local_state = preferred_sync_state(config, deck_uuid.as_ref());
    let local_settings_available = local_sync_settings_available(config, deck_uuid.as_ref());
    let remote_status = remote_status_override.map_or_else(
        || remote_sync_status(deck_uuid.as_ref(), local_settings_available),
        RemoteSyncStatus::from,
    );

    let mut details = String::new();
    push_section_heading(&mut details, localizer.sync_status_actions_section_label());
    let upload_status = sync_upload_status(localizer, deck_uuid.as_ref(), config, &remote_status);
    push_detail_line(
        &mut details,
        localizer.sync_status_upload_label(),
        &upload_status,
    );
    let download_status =
        sync_download_status(localizer, deck_uuid.as_ref(), config, &remote_status);
    push_detail_line(
        &mut details,
        localizer.sync_status_download_label(),
        &download_status,
    );

    push_section_heading(&mut details, localizer.sync_status_remote_section_label());
    push_remote_status_lines(&mut details, localizer, &remote_status, config);
    if let Some(quota_override) = quota_override {
        push_remote_quota_lines(&mut details, localizer, quota_override.into());
    }

    push_section_heading(&mut details, localizer.sync_status_local_section_label());
    push_detail_line(
        &mut details,
        localizer.deck_properties_label(Detail::Visibility),
        deck_visibility_label(localizer, metadata.deck_visibility()),
    );
    push_detail_line(
        &mut details,
        localizer.options_sync_server_url_label(),
        config.sync_server_url.as_deref().unwrap_or(none),
    );
    push_detail_line(
        &mut details,
        localizer.options_local_device_name_label(),
        config.local_device_name.as_deref().unwrap_or(none),
    );
    let sync_owner_identity = config
        .sync_owner_identity
        .as_deref()
        .or_else(|| local_state.and_then(LocalDeckSyncState::owner_identity));
    let sync_owner_display = sync_owner_identity.map(display_owner_identity);
    push_detail_line(
        &mut details,
        localizer.deck_properties_label(Detail::SyncOwnerIdentity),
        sync_owner_display.as_deref().unwrap_or(none),
    );

    push_section_heading(
        &mut details,
        localizer.sync_details_technical_section_label(),
    );
    push_detail_line(
        &mut details,
        localizer.deck_properties_label(Detail::DeckUuid),
        deck_uuid
            .as_ref()
            .map(ToString::to_string)
            .as_deref()
            .unwrap_or(none),
    );
    push_remote_technical_lines(&mut details, localizer, &remote_status);
    push_detail_line(
        &mut details,
        localizer.sync_status_last_seen_remote_revision_label(),
        local_state
            .and_then(LocalDeckSyncState::last_seen_remote_revision)
            .unwrap_or(none),
    );
    push_detail_line(
        &mut details,
        localizer.sync_status_last_uploaded_hash_label(),
        local_state
            .and_then(LocalDeckSyncState::last_uploaded_local_bundle_hash)
            .unwrap_or(none),
    );
    push_detail_line(
        &mut details,
        localizer.sync_status_last_downloaded_hash_label(),
        local_state
            .and_then(LocalDeckSyncState::last_downloaded_remote_bundle_hash)
            .unwrap_or(none),
    );
    details
}

fn preferred_sync_state<'a>(
    config: &'a AppConfig,
    deck_uuid: Option<&cosimo::SyncUuid>,
) -> Option<&'a LocalDeckSyncState> {
    let deck_uuid = deck_uuid?;
    let sync_server_url = config.sync_server_url.as_deref()?;
    if let Some(owner_identity) = config.sync_owner_identity.as_deref() {
        return config.deck_sync_states.iter().find(|state| {
            state.deck_uuid() == deck_uuid
                && state.sync_server_url() == sync_server_url
                && state.owner_identity() == Some(owner_identity)
        });
    }
    config
        .deck_sync_states
        .iter()
        .filter(|state| {
            state.deck_uuid() == deck_uuid && state.sync_server_url() == sync_server_url
        })
        .max_by_key(|state| revision_sort_key(state.last_seen_remote_revision()))
}

fn revision_sort_key(revision: Option<&str>) -> (bool, u64, &str) {
    let Some(revision) = revision else {
        return (false, 0, "");
    };
    match revision.parse::<u64>() {
        Ok(number) => (true, number, revision),
        Err(_) => (false, 0, revision),
    }
}

fn local_sync_settings_available(config: &AppConfig, deck_uuid: Option<&cosimo::SyncUuid>) -> bool {
    deck_uuid.is_some()
        && config.sync_server_url.is_some()
        && config.sync_owner_identity.is_some()
        && config.sync_bearer_token.is_some()
}

fn push_remote_status_lines(
    details: &mut String,
    localizer: Localizer,
    status: &RemoteSyncStatus,
    config: &AppConfig,
) {
    match status {
        RemoteSyncStatus::NotCheckedNoDeckUuid => push_detail_line(
            details,
            localizer.sync_status_remote_deck_label(),
            localizer.sync_status_unavailable_no_deck_uuid(),
        ),
        RemoteSyncStatus::NotCheckedNoLocalSettings => push_detail_line(
            details,
            localizer.sync_status_remote_deck_label(),
            localizer.sync_status_unavailable_missing_local_settings(),
        ),
        RemoteSyncStatus::NotFound => push_detail_line(
            details,
            localizer.sync_status_remote_deck_label(),
            localizer.sync_status_not_found(),
        ),
        RemoteSyncStatus::ReadError(error) => push_detail_line(
            details,
            localizer.sync_status_remote_deck_label(),
            &localizer.sync_status_remote_read_error(error),
        ),
        RemoteSyncStatus::AuthenticationFailed => push_detail_line(
            details,
            localizer.sync_status_remote_deck_label(),
            localizer.sync_status_authentication_failed(),
        ),
        RemoteSyncStatus::Found(status) => {
            let owner_access = remote_status_has_owner_access(config, status);
            push_found_remote_status_lines(details, localizer, status, owner_access);
        }
        RemoteSyncStatus::FoundWithOwnerAuthenticationFailed { status } => {
            push_found_remote_status_lines(
                details,
                localizer,
                status,
                RemoteOwnerAccess::AuthenticationFailed,
            );
        }
        RemoteSyncStatus::FoundWithOwnerReadError {
            status,
            owner_error,
        } => {
            push_found_remote_status_lines(
                details,
                localizer,
                status,
                RemoteOwnerAccess::ReadError(owner_error),
            );
        }
    }
}

fn push_found_remote_status_lines(
    details: &mut String,
    localizer: Localizer,
    status: &SyncRemoteDeckStatus,
    owner_access: RemoteOwnerAccess<'_>,
) {
    push_detail_line(
        details,
        localizer.sync_status_remote_deck_label(),
        localizer.sync_status_found(),
    );
    push_detail_line(
        details,
        localizer.sync_status_remote_visibility_label(),
        deck_visibility_label(localizer, status.visibility()),
    );
    push_detail_line(
        details,
        localizer.sync_status_remote_owner_identity_label(),
        &display_owner_identity(status.owner_identity()),
    );
    push_detail_line(
        details,
        localizer.sync_status_remote_study_history_label(),
        &remote_study_history_text(localizer, status, owner_access),
    );
    push_detail_line(
        details,
        localizer.sync_status_remote_last_upload_device_label(),
        &last_upload_device_text(localizer, status, owner_access),
    );
}

fn remote_study_history_text(
    localizer: Localizer,
    status: &SyncRemoteDeckStatus,
    owner_access: RemoteOwnerAccess<'_>,
) -> String {
    match owner_access {
        RemoteOwnerAccess::Unavailable => {
            localizer.sync_status_unavailable_owner_only().to_string()
        }
        RemoteOwnerAccess::ReadError(error) => {
            localizer.sync_status_unavailable_remote_read_error(error)
        }
        RemoteOwnerAccess::AuthenticationFailed => localizer
            .sync_status_unavailable_authentication_failed()
            .to_string(),
        RemoteOwnerAccess::Available => {
            if status.has_study_history() {
                localizer.sync_status_available().to_string()
            } else {
                localizer.card_details_none().to_string()
            }
        }
    }
}

fn push_remote_technical_lines(
    details: &mut String,
    localizer: Localizer,
    status: &RemoteSyncStatus,
) {
    let status = match status {
        RemoteSyncStatus::Found(status) => status,
        RemoteSyncStatus::FoundWithOwnerAuthenticationFailed { status } => status,
        RemoteSyncStatus::FoundWithOwnerReadError { status, .. } => status,
        _ => return,
    };
    push_detail_line(
        details,
        localizer.sync_status_remote_revision_label(),
        status.revision(),
    );
}

fn push_remote_quota_lines(
    details: &mut String,
    localizer: Localizer,
    status: RemoteSyncQuotaStatus,
) {
    match status {
        RemoteSyncQuotaStatus::AuthenticationFailed => push_detail_line(
            details,
            localizer.sync_status_remote_deck_quota_label(),
            localizer.sync_status_authentication_failed(),
        ),
        RemoteSyncQuotaStatus::ReadError(error) => push_detail_line(
            details,
            localizer.sync_status_remote_deck_quota_label(),
            &localizer.sync_status_remote_read_error(&error),
        ),
        RemoteSyncQuotaStatus::Found(status) => {
            push_detail_line(
                details,
                localizer.sync_status_remote_deck_quota_label(),
                &remote_deck_quota_text(localizer, &status),
            );
            push_detail_line(
                details,
                localizer.sync_status_remote_storage_quota_label(),
                &remote_storage_quota_text(localizer, &status),
            );
            if let Some(limit) = status.max_storage_bytes_total() {
                push_detail_line(
                    details,
                    localizer.sync_status_remote_server_storage_limit_label(),
                    &format_bytes(limit),
                );
            }
        }
    }
}

fn remote_deck_quota_text(localizer: Localizer, status: &SyncQuotaStatus) -> String {
    match (status.owner_deck_count(), status.max_decks_per_owner()) {
        (Some(used), Some(limit)) => localizer.sync_status_deck_quota_used(used, limit),
        (Some(used), None) => localizer.sync_status_deck_quota_used_unlimited(used),
        (None, Some(limit)) => localizer.sync_status_deck_quota_limit(limit),
        (None, None) => localizer.sync_status_deck_quota_unlimited().to_string(),
    }
}

fn remote_storage_quota_text(localizer: Localizer, status: &SyncQuotaStatus) -> String {
    match (
        status.owner_storage_bytes(),
        status.max_storage_bytes_per_owner(),
    ) {
        (Some(used), Some(limit)) => {
            localizer.sync_status_storage_quota_used(&format_bytes(used), &format_bytes(limit))
        }
        (Some(used), None) => {
            localizer.sync_status_storage_quota_used_unlimited(&format_bytes(used))
        }
        (None, Some(limit)) => localizer.sync_status_storage_quota_limit(&format_bytes(limit)),
        (None, None) => localizer.sync_status_storage_quota_unlimited().to_string(),
    }
}

fn format_bytes(bytes: u64) -> String {
    const UNITS: &[(&str, f64)] = &[
        ("GiB", 1024.0 * 1024.0 * 1024.0),
        ("MiB", 1024.0 * 1024.0),
        ("KiB", 1024.0),
    ];
    for (unit, size) in UNITS {
        if bytes >= *size as u64 {
            let value = bytes as f64 / size;
            return if value >= 10.0 {
                format!("{value:.0} {unit}")
            } else {
                format!("{value:.1} {unit}")
            };
        }
    }
    format!("{bytes} bytes")
}

#[derive(Clone, Copy)]
enum RemoteOwnerAccess<'a> {
    Available,
    Unavailable,
    AuthenticationFailed,
    ReadError(&'a str),
}

fn last_upload_device_text(
    localizer: Localizer,
    status: &SyncRemoteDeckStatus,
    owner_access: RemoteOwnerAccess<'_>,
) -> String {
    match owner_access {
        RemoteOwnerAccess::Unavailable => {
            return localizer.sync_status_unavailable_owner_only().to_string();
        }
        RemoteOwnerAccess::ReadError(error) => {
            return localizer.sync_status_unavailable_remote_read_error(error);
        }
        RemoteOwnerAccess::AuthenticationFailed => {
            return localizer
                .sync_status_unavailable_authentication_failed()
                .to_string();
        }
        RemoteOwnerAccess::Available => {}
    }
    match status.last_upload_device_name() {
        Some(name) => name.to_string(),
        None => localizer.card_details_none().to_string(),
    }
}

enum RemoteSyncStatus {
    NotCheckedNoDeckUuid,
    NotCheckedNoLocalSettings,
    NotFound,
    AuthenticationFailed,
    ReadError(String),
    Found(Box<cosimo::SyncRemoteDeckStatus>),
    FoundWithOwnerAuthenticationFailed {
        status: Box<cosimo::SyncRemoteDeckStatus>,
    },
    FoundWithOwnerReadError {
        status: Box<cosimo::SyncRemoteDeckStatus>,
        owner_error: String,
    },
}

enum RemoteSyncQuotaStatus {
    AuthenticationFailed,
    ReadError(String),
    Found(cosimo::SyncQuotaStatus),
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
struct SyncClientFacts {
    has_deck_uuid: bool,
    has_sync_server_url: bool,
    has_owner_identity: bool,
    has_bearer_token: bool,
}

impl SyncClientFacts {
    fn from_config(config: &AppConfig, deck_uuid: Option<&cosimo::SyncUuid>) -> Self {
        Self {
            has_deck_uuid: deck_uuid.is_some(),
            has_sync_server_url: config.sync_server_url.is_some(),
            has_owner_identity: config.sync_owner_identity.is_some(),
            has_bearer_token: config.sync_bearer_token.is_some(),
        }
    }

    fn has_login(self) -> bool {
        self.has_owner_identity && self.has_bearer_token
    }
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum RemoteDeckObservation<'a> {
    NotCheckedNoDeckUuid,
    NotCheckedNoLocalSettings,
    NotFound,
    AuthenticationFailed,
    ReadError(&'a str),
    Found {
        owner_identity: &'a str,
        owner_matches_login: bool,
        has_study_history: bool,
    },
    FoundWithOwnerAuthenticationFailed,
    FoundWithOwnerReadError {
        owner_error: &'a str,
    },
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum SyncUploadDecision<'a> {
    Available,
    MissingSyncServerUrl,
    MissingLogin,
    StaleLogin,
    RemoteReadError(&'a str),
    OwnerReadError(&'a str),
    RemoteOwnerMismatch {
        remote_owner: &'a str,
        current_owner: &'a str,
    },
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum SyncDownloadDecision<'a> {
    AvailableUnchecked,
    AvailableContentOnly,
    AvailableWithStudyHistory,
    MissingDeckUuid,
    MissingSyncServerUrl,
    StaleLogin,
    RemoteReadError(&'a str),
    RemoteNotFound,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
struct SyncActionDecision<'a> {
    upload: SyncUploadDecision<'a>,
    download: SyncDownloadDecision<'a>,
}

impl From<RemoteSyncQuotaOverride> for RemoteSyncQuotaStatus {
    fn from(status: RemoteSyncQuotaOverride) -> Self {
        match status {
            RemoteSyncQuotaOverride::AuthenticationFailed => Self::AuthenticationFailed,
            RemoteSyncQuotaOverride::ReadError(error) => Self::ReadError(error),
            RemoteSyncQuotaOverride::Found(status) => Self::Found(status),
        }
    }
}

impl From<RemoteSyncStatusOverride> for RemoteSyncStatus {
    fn from(status: RemoteSyncStatusOverride) -> Self {
        match status {
            RemoteSyncStatusOverride::NotFound => Self::NotFound,
            RemoteSyncStatusOverride::AuthenticationFailed => Self::AuthenticationFailed,
            RemoteSyncStatusOverride::ReadError(error) => Self::ReadError(error),
            RemoteSyncStatusOverride::Found(status) => Self::Found(status),
            RemoteSyncStatusOverride::FoundWithOwnerAuthenticationFailed { status } => {
                Self::FoundWithOwnerAuthenticationFailed { status }
            }
            RemoteSyncStatusOverride::FoundWithOwnerReadError {
                status,
                owner_error,
            } => Self::FoundWithOwnerReadError {
                status,
                owner_error,
            },
        }
    }
}

fn remote_sync_status(
    deck_uuid: Option<&cosimo::SyncUuid>,
    local_settings_available: bool,
) -> RemoteSyncStatus {
    if deck_uuid.is_none() {
        return RemoteSyncStatus::NotCheckedNoDeckUuid;
    }
    if !local_settings_available {
        return RemoteSyncStatus::NotCheckedNoLocalSettings;
    }
    RemoteSyncStatus::NotCheckedNoLocalSettings
}

fn remote_deck_observation<'a>(
    config: &'a AppConfig,
    status: &'a RemoteSyncStatus,
) -> RemoteDeckObservation<'a> {
    match status {
        RemoteSyncStatus::NotCheckedNoDeckUuid => RemoteDeckObservation::NotCheckedNoDeckUuid,
        RemoteSyncStatus::NotCheckedNoLocalSettings => {
            RemoteDeckObservation::NotCheckedNoLocalSettings
        }
        RemoteSyncStatus::NotFound => RemoteDeckObservation::NotFound,
        RemoteSyncStatus::AuthenticationFailed => RemoteDeckObservation::AuthenticationFailed,
        RemoteSyncStatus::ReadError(error) => RemoteDeckObservation::ReadError(error),
        RemoteSyncStatus::Found(status) => RemoteDeckObservation::Found {
            owner_identity: status.owner_identity(),
            owner_matches_login: config.sync_bearer_token.is_some()
                && config.sync_owner_identity.as_deref() == Some(status.owner_identity()),
            has_study_history: status.has_study_history(),
        },
        RemoteSyncStatus::FoundWithOwnerAuthenticationFailed { .. } => {
            RemoteDeckObservation::FoundWithOwnerAuthenticationFailed
        }
        RemoteSyncStatus::FoundWithOwnerReadError {
            status: _,
            owner_error,
        } => RemoteDeckObservation::FoundWithOwnerReadError { owner_error },
    }
}

fn sync_action_decision<'a>(
    facts: SyncClientFacts,
    current_owner_identity: Option<&'a str>,
    remote: RemoteDeckObservation<'a>,
) -> SyncActionDecision<'a> {
    SyncActionDecision {
        upload: sync_upload_decision(facts, current_owner_identity, remote),
        download: sync_download_decision(facts, remote),
    }
}

fn sync_upload_decision<'a>(
    facts: SyncClientFacts,
    current_owner_identity: Option<&'a str>,
    remote: RemoteDeckObservation<'a>,
) -> SyncUploadDecision<'a> {
    if !facts.has_sync_server_url {
        return SyncUploadDecision::MissingSyncServerUrl;
    }
    if !facts.has_login() {
        return SyncUploadDecision::MissingLogin;
    }
    match remote {
        RemoteDeckObservation::AuthenticationFailed
        | RemoteDeckObservation::FoundWithOwnerAuthenticationFailed => {
            SyncUploadDecision::StaleLogin
        }
        RemoteDeckObservation::ReadError(error) => SyncUploadDecision::RemoteReadError(error),
        RemoteDeckObservation::Found {
            owner_identity,
            owner_matches_login: false,
            ..
        } => SyncUploadDecision::RemoteOwnerMismatch {
            remote_owner: owner_identity,
            current_owner: current_owner_identity.unwrap_or(""),
        },
        RemoteDeckObservation::FoundWithOwnerReadError { owner_error } => {
            SyncUploadDecision::OwnerReadError(owner_error)
        }
        RemoteDeckObservation::NotCheckedNoDeckUuid
        | RemoteDeckObservation::NotCheckedNoLocalSettings
        | RemoteDeckObservation::NotFound
        | RemoteDeckObservation::Found { .. } => SyncUploadDecision::Available,
    }
}

fn sync_download_decision<'a>(
    facts: SyncClientFacts,
    remote: RemoteDeckObservation<'a>,
) -> SyncDownloadDecision<'a> {
    if !facts.has_deck_uuid {
        return SyncDownloadDecision::MissingDeckUuid;
    }
    if !facts.has_sync_server_url {
        return SyncDownloadDecision::MissingSyncServerUrl;
    }
    match remote {
        RemoteDeckObservation::AuthenticationFailed => SyncDownloadDecision::StaleLogin,
        RemoteDeckObservation::ReadError(error) => SyncDownloadDecision::RemoteReadError(error),
        RemoteDeckObservation::NotFound => SyncDownloadDecision::RemoteNotFound,
        RemoteDeckObservation::Found {
            owner_matches_login: true,
            has_study_history: true,
            ..
        } => SyncDownloadDecision::AvailableWithStudyHistory,
        RemoteDeckObservation::Found { .. }
        | RemoteDeckObservation::FoundWithOwnerAuthenticationFailed
        | RemoteDeckObservation::FoundWithOwnerReadError { .. } => {
            SyncDownloadDecision::AvailableContentOnly
        }
        RemoteDeckObservation::NotCheckedNoDeckUuid
        | RemoteDeckObservation::NotCheckedNoLocalSettings => {
            SyncDownloadDecision::AvailableUnchecked
        }
    }
}

fn sync_upload_status(
    localizer: Localizer,
    deck_uuid: Option<&cosimo::SyncUuid>,
    config: &AppConfig,
    remote_status: &RemoteSyncStatus,
) -> String {
    let facts = SyncClientFacts::from_config(config, deck_uuid);
    let decision = sync_action_decision(
        facts,
        config.sync_owner_identity.as_deref(),
        remote_deck_observation(config, remote_status),
    )
    .upload;
    match decision {
        SyncUploadDecision::Available => localizer.sync_status_available().to_string(),
        SyncUploadDecision::MissingSyncServerUrl => localizer
            .sync_status_unavailable_missing_sync_server_url()
            .to_string(),
        SyncUploadDecision::MissingLogin => localizer
            .sync_status_unavailable_missing_local_settings()
            .to_string(),
        SyncUploadDecision::StaleLogin => localizer
            .sync_status_unavailable_authentication_failed()
            .to_string(),
        SyncUploadDecision::RemoteReadError(error) | SyncUploadDecision::OwnerReadError(error) => {
            localizer.sync_status_unavailable_remote_read_error(error)
        }
        SyncUploadDecision::RemoteOwnerMismatch {
            remote_owner,
            current_owner,
        } => {
            let remote_owner = display_owner_identity(remote_owner);
            let current_owner = display_owner_identity(current_owner);
            localizer.sync_upload_owner_mismatch_message(&remote_owner, &current_owner)
        }
    }
}

fn sync_download_status(
    localizer: Localizer,
    deck_uuid: Option<&cosimo::SyncUuid>,
    config: &AppConfig,
    remote_status: &RemoteSyncStatus,
) -> String {
    let facts = SyncClientFacts::from_config(config, deck_uuid);
    let decision = sync_action_decision(
        facts,
        config.sync_owner_identity.as_deref(),
        remote_deck_observation(config, remote_status),
    )
    .download;
    match decision {
        SyncDownloadDecision::AvailableUnchecked => localizer.sync_status_available().to_string(),
        SyncDownloadDecision::AvailableContentOnly => {
            localizer.sync_status_available_content_only().to_string()
        }
        SyncDownloadDecision::AvailableWithStudyHistory => localizer
            .sync_status_available_with_study_history()
            .to_string(),
        SyncDownloadDecision::MissingDeckUuid => {
            localizer.sync_status_unavailable_no_deck_uuid().to_string()
        }
        SyncDownloadDecision::MissingSyncServerUrl => localizer
            .sync_status_unavailable_missing_sync_server_url()
            .to_string(),
        SyncDownloadDecision::StaleLogin => localizer
            .sync_status_unavailable_authentication_failed()
            .to_string(),
        SyncDownloadDecision::RemoteReadError(error) => {
            localizer.sync_status_unavailable_remote_read_error(error)
        }
        SyncDownloadDecision::RemoteNotFound => localizer
            .sync_status_unavailable_remote_not_found()
            .to_string(),
    }
}

fn remote_status_has_owner_access(
    config: &AppConfig,
    status: &SyncRemoteDeckStatus,
) -> RemoteOwnerAccess<'static> {
    if config.sync_bearer_token.is_some()
        && config.sync_owner_identity.as_deref() == Some(status.owner_identity())
    {
        RemoteOwnerAccess::Available
    } else {
        RemoteOwnerAccess::Unavailable
    }
}

fn deck_visibility_label(localizer: Localizer, visibility: DeckVisibility) -> &'static str {
    match visibility {
        DeckVisibility::Private => localizer.deck_visibility_private_choice(),
        DeckVisibility::Unlisted => localizer.deck_visibility_unlisted_choice(),
        DeckVisibility::Public => localizer.deck_visibility_public_choice(),
    }
}

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');
}

fn push_section_heading(details: &mut String, heading: &str) {
    if !details.is_empty() {
        details.push('\n');
    }
    details.push_str(heading);
    details.push('\n');
}

#[cfg(test)]
mod tests {
    use super::*;
    use cosimo::{SyncSharedDeckStatus, SyncUuid};

    fn assert_before(text: &str, first: &str, second: &str) {
        let first_index = text
            .find(first)
            .unwrap_or_else(|| panic!("missing first text: {first}"));
        let second_index = text
            .find(second)
            .unwrap_or_else(|| panic!("missing second text: {second}"));
        assert!(
            first_index < second_index,
            "expected `{first}` before `{second}` in:\n{text}"
        );
    }

    fn facts(
        has_deck_uuid: bool,
        has_sync_server_url: bool,
        has_owner_identity: bool,
        has_bearer_token: bool,
    ) -> SyncClientFacts {
        SyncClientFacts {
            has_deck_uuid,
            has_sync_server_url,
            has_owner_identity,
            has_bearer_token,
        }
    }

    fn remote_status_for_test(
        deck_uuid: SyncUuid,
        owner_identity: &str,
        study_history_hash: Option<&str>,
    ) -> SyncRemoteDeckStatus {
        SyncRemoteDeckStatus::from_shared_status(
            SyncSharedDeckStatus::from_parts(
                deck_uuid,
                "17",
                DeckVisibility::Public,
                owner_identity,
                "sha256:content",
            )
            .unwrap(),
            study_history_hash,
            Some("test-device"),
        )
        .unwrap()
    }

    #[test]
    fn sync_action_decision_covers_local_and_remote_state_combinations() {
        struct Case {
            name: &'static str,
            facts: SyncClientFacts,
            current_owner: Option<&'static str>,
            remote: RemoteDeckObservation<'static>,
            upload: SyncUploadDecision<'static>,
            download: SyncDownloadDecision<'static>,
        }

        let cases = [
            Case {
                name: "no deck uuid",
                facts: facts(false, true, true, true),
                current_owner: Some("acct:current@example.invalid"),
                remote: RemoteDeckObservation::NotCheckedNoDeckUuid,
                upload: SyncUploadDecision::Available,
                download: SyncDownloadDecision::MissingDeckUuid,
            },
            Case {
                name: "no sync server url",
                facts: facts(true, false, true, true),
                current_owner: Some("acct:current@example.invalid"),
                remote: RemoteDeckObservation::NotCheckedNoLocalSettings,
                upload: SyncUploadDecision::MissingSyncServerUrl,
                download: SyncDownloadDecision::MissingSyncServerUrl,
            },
            Case {
                name: "server and deck but no login",
                facts: facts(true, true, false, false),
                current_owner: None,
                remote: RemoteDeckObservation::NotCheckedNoLocalSettings,
                upload: SyncUploadDecision::MissingLogin,
                download: SyncDownloadDecision::AvailableUnchecked,
            },
            Case {
                name: "partial login without bearer token",
                facts: facts(true, true, true, false),
                current_owner: Some("acct:current@example.invalid"),
                remote: RemoteDeckObservation::NotCheckedNoLocalSettings,
                upload: SyncUploadDecision::MissingLogin,
                download: SyncDownloadDecision::AvailableUnchecked,
            },
            Case {
                name: "authenticated but remote unchecked",
                facts: facts(true, true, true, true),
                current_owner: Some("acct:current@example.invalid"),
                remote: RemoteDeckObservation::NotCheckedNoLocalSettings,
                upload: SyncUploadDecision::Available,
                download: SyncDownloadDecision::AvailableUnchecked,
            },
            Case {
                name: "authenticated remote read error",
                facts: facts(true, true, true, true),
                current_owner: Some("acct:current@example.invalid"),
                remote: RemoteDeckObservation::ReadError("timeout"),
                upload: SyncUploadDecision::RemoteReadError("timeout"),
                download: SyncDownloadDecision::RemoteReadError("timeout"),
            },
            Case {
                name: "unauthenticated remote read error",
                facts: facts(true, true, false, false),
                current_owner: None,
                remote: RemoteDeckObservation::ReadError("timeout"),
                upload: SyncUploadDecision::MissingLogin,
                download: SyncDownloadDecision::RemoteReadError("timeout"),
            },
            Case {
                name: "authenticated remote deck missing",
                facts: facts(true, true, true, true),
                current_owner: Some("acct:current@example.invalid"),
                remote: RemoteDeckObservation::NotFound,
                upload: SyncUploadDecision::Available,
                download: SyncDownloadDecision::RemoteNotFound,
            },
            Case {
                name: "unauthenticated remote deck missing",
                facts: facts(true, true, false, false),
                current_owner: None,
                remote: RemoteDeckObservation::NotFound,
                upload: SyncUploadDecision::MissingLogin,
                download: SyncDownloadDecision::RemoteNotFound,
            },
            Case {
                name: "owner deck with study history",
                facts: facts(true, true, true, true),
                current_owner: Some("acct:current@example.invalid"),
                remote: RemoteDeckObservation::Found {
                    owner_identity: "acct:current@example.invalid",
                    owner_matches_login: true,
                    has_study_history: true,
                },
                upload: SyncUploadDecision::Available,
                download: SyncDownloadDecision::AvailableWithStudyHistory,
            },
            Case {
                name: "owner deck without study history",
                facts: facts(true, true, true, true),
                current_owner: Some("acct:current@example.invalid"),
                remote: RemoteDeckObservation::Found {
                    owner_identity: "acct:current@example.invalid",
                    owner_matches_login: true,
                    has_study_history: false,
                },
                upload: SyncUploadDecision::Available,
                download: SyncDownloadDecision::AvailableContentOnly,
            },
            Case {
                name: "other owner deck",
                facts: facts(true, true, true, true),
                current_owner: Some("acct:current@example.invalid"),
                remote: RemoteDeckObservation::Found {
                    owner_identity: "acct:other@example.invalid",
                    owner_matches_login: false,
                    has_study_history: true,
                },
                upload: SyncUploadDecision::RemoteOwnerMismatch {
                    remote_owner: "acct:other@example.invalid",
                    current_owner: "acct:current@example.invalid",
                },
                download: SyncDownloadDecision::AvailableContentOnly,
            },
            Case {
                name: "owner read failed after shared status",
                facts: facts(true, true, true, true),
                current_owner: Some("acct:current@example.invalid"),
                remote: RemoteDeckObservation::FoundWithOwnerReadError {
                    owner_error: "request timed out",
                },
                upload: SyncUploadDecision::OwnerReadError("request timed out"),
                download: SyncDownloadDecision::AvailableContentOnly,
            },
            Case {
                name: "saved login no longer valid",
                facts: facts(true, true, true, true),
                current_owner: Some("acct:current@example.invalid"),
                remote: RemoteDeckObservation::AuthenticationFailed,
                upload: SyncUploadDecision::StaleLogin,
                download: SyncDownloadDecision::StaleLogin,
            },
            Case {
                name: "owner authentication failed after shared status",
                facts: facts(true, true, true, true),
                current_owner: Some("acct:current@example.invalid"),
                remote: RemoteDeckObservation::FoundWithOwnerAuthenticationFailed,
                upload: SyncUploadDecision::StaleLogin,
                download: SyncDownloadDecision::AvailableContentOnly,
            },
        ];

        for case in cases {
            let decision = sync_action_decision(case.facts, case.current_owner, case.remote);
            assert_eq!(
                decision.upload, case.upload,
                "unexpected upload decision for {}",
                case.name
            );
            assert_eq!(
                decision.download, case.download,
                "unexpected download decision for {}",
                case.name
            );
        }
    }

    #[test]
    fn sync_status_reports_missing_uuid_and_settings() {
        let metadata = DeckMetadata::v2();
        let text = sync_status_text(Localizer::default(), &metadata, &AppConfig::default());

        assert!(text.contains("Deck UUID: None"));
        assert!(text.contains("Sync server URL: https://cosimo.isonomia.net/sync.cgi"));
        assert!(text.contains(
            "Upload with study history: Not available: log in to the sync server first."
        ));
        assert!(text.contains("Download synced deck: Not available: this deck has no deck UUID."));
    }

    #[test]
    fn sync_status_allows_upload_without_uuid_when_account_is_known() {
        let metadata = DeckMetadata::v2();
        let config = AppConfig {
            sync_owner_identity: Some("acct:modulus@example.invalid".to_string()),
            sync_bearer_token: Some("cosimo_token".to_string()),
            ..AppConfig::default()
        };

        let text = sync_status_text(Localizer::default(), &metadata, &config);

        assert!(text.contains("Sync account: @modulus@example.invalid"));
        assert!(text.contains("Upload with study history: Available"));
        assert!(text.contains("Download synced deck: Not available: this deck has no deck UUID."));
        assert!(!text.contains(
            "Upload with study history: Not available: log in to the sync server first."
        ));
    }

    #[test]
    fn sync_status_reports_configured_http_state_and_remote_override() {
        let mut metadata = DeckMetadata::v2();
        let deck_uuid = "550e8400-e29b-41d4-a716-446655440000"
            .parse::<SyncUuid>()
            .unwrap();
        metadata.set_deck_uuid(&deck_uuid);
        metadata.set_deck_visibility(DeckVisibility::Unlisted);
        let sync_server_url = "http://127.0.0.1:44791";
        let remote_status = SyncRemoteDeckStatus::from_shared_status(
            SyncSharedDeckStatus::from_parts(
                deck_uuid.clone(),
                "43",
                DeckVisibility::Unlisted,
                "acct:modulus@example.invalid",
                "sha256:content",
            )
            .unwrap(),
            Some("sha256:history"),
            Some("desktop"),
        )
        .unwrap();
        let mut config = AppConfig {
            sync_server_url: Some(sync_server_url.to_string()),
            sync_owner_identity: Some("acct:modulus@example.invalid".to_string()),
            sync_bearer_token: Some("cosimo_token".to_string()),
            local_device_name: Some("laptop".to_string()),
            ..AppConfig::default()
        };
        config.deck_sync_states.push(
            LocalDeckSyncState::from_parts(
                deck_uuid.clone(),
                sync_server_url,
                Some("acct:modulus@example.invalid"),
                Some("42"),
                Some("sha256:upload"),
                Some("sha256:download"),
            )
            .unwrap(),
        );

        let text = sync_status_text_with_remote_and_quota(
            Localizer::default(),
            &metadata,
            &config,
            Some(RemoteSyncStatusOverride::Found(Box::new(remote_status))),
            Some(RemoteSyncQuotaOverride::Found(SyncQuotaStatus::from_parts(
                Some(10),
                Some(1024 * 1024),
                Some(10 * 1024 * 1024),
                Some(2),
                Some(1536),
            ))),
        );

        assert!(text.contains("Deck UUID: 550e8400-e29b-41d4-a716-446655440000"));
        assert!(text.contains("Visibility: Unlisted"));
        assert!(text.contains("Sync server URL: http://127.0.0.1:44791"));
        assert!(text.contains("Sync account: @modulus@example.invalid"));
        assert!(text.contains("Local device name: laptop"));
        assert!(text.contains("Last seen remote revision: 42"));
        assert!(text.contains("Last uploaded bundle hash: sha256:upload"));
        assert!(text.contains("Last downloaded bundle hash: sha256:download"));
        assert!(text.contains("Upload with study history: Available"));
        assert!(text.contains("Download synced deck: Available: deck and study history"));
        assert!(text.contains("Remote deck: Found"));
        assert!(text.contains("Remote revision: 43"));
        assert!(text.contains("Remote owner: @modulus@example.invalid"));
        assert!(text.contains("Remote visibility: Unlisted"));
        assert!(text.contains("Remote study history: Available"));
        assert!(text.contains("Remote last upload device: desktop"));
        assert!(text.contains("Remote deck quota: 2 of 10 decks used"));
        assert!(text.contains("Remote storage quota: 1.5 KiB of 1.0 MiB used"));
        assert!(text.contains("Remote server storage limit: 10 MiB"));
        assert_before(&text, "Available actions", "Technical details");
        assert_before(&text, "Upload with study history: Available", "Deck UUID:");
        assert_before(&text, "Remote visibility: Unlisted", "Remote revision: 43");
        assert_before(&text, "Remote deck quota:", "Deck UUID:");
    }

    #[test]
    fn sync_status_reports_announced_quota_limits_without_login() {
        let metadata = DeckMetadata::v2();
        let config = AppConfig {
            sync_server_url: Some("http://127.0.0.1:44791".to_string()),
            ..AppConfig::default()
        };

        let text = sync_status_text_with_remote_and_quota(
            Localizer::default(),
            &metadata,
            &config,
            None,
            Some(RemoteSyncQuotaOverride::Found(SyncQuotaStatus::from_parts(
                Some(10),
                Some(1024 * 1024),
                Some(10 * 1024 * 1024),
                None,
                None,
            ))),
        );

        assert!(text.contains("Remote deck quota: Limit: 10 decks per owner"));
        assert!(text.contains("Remote storage quota: Limit: 1.0 MiB per owner"));
        assert!(text.contains("Remote server storage limit: 10 MiB"));
    }

    #[test]
    fn sync_status_reports_global_authenticated_sync_settings() {
        let mut metadata = DeckMetadata::v2();
        let deck_uuid = "550e8400-e29b-41d4-a716-446655440000"
            .parse::<SyncUuid>()
            .unwrap();
        metadata.set_deck_uuid(&deck_uuid);
        let config = AppConfig {
            sync_server_url: Some("http://127.0.0.1:44791".to_string()),
            sync_owner_identity: Some("acct:modulus@example.invalid".to_string()),
            sync_bearer_token: Some("cosimo_token".to_string()),
            ..AppConfig::default()
        };

        let text = sync_status_text(Localizer::default(), &metadata, &config);

        assert!(text.contains("Sync account: @modulus@example.invalid"));
        assert!(text.contains("Upload with study history: Available"));
        assert!(text.contains("Download synced deck: Available"));
    }

    #[test]
    fn sync_status_marks_actions_unavailable_when_authenticated_remote_check_fails() {
        let mut metadata = DeckMetadata::v2();
        let deck_uuid = "550e8400-e29b-41d4-a716-446655440000"
            .parse::<SyncUuid>()
            .unwrap();
        metadata.set_deck_uuid(&deck_uuid);
        let config = AppConfig {
            sync_server_url: Some("https://sync.example.invalid".to_string()),
            sync_owner_identity: Some("acct:modulus@example.invalid".to_string()),
            sync_bearer_token: Some("invalid_cosimo_token".to_string()),
            ..AppConfig::default()
        };

        let text = sync_status_text_with_remote(
            Localizer::default(),
            &metadata,
            &config,
            Some(RemoteSyncStatusOverride::ReadError(
                "request timed out".to_string(),
            )),
        );

        assert!(text.contains(
            "Upload with study history: Not available: could not check remote sync state: request timed out"
        ));
        assert!(text.contains(
            "Download synced deck: Not available: could not check remote sync state: request timed out"
        ));
        assert!(text.contains("Remote deck: Could not read remote sync state: request timed out"));
    }

    #[test]
    fn sync_status_reports_invalid_saved_login_without_protocol_noise() {
        let mut metadata = DeckMetadata::v2();
        let deck_uuid = "550e8400-e29b-41d4-a716-446655440000"
            .parse::<SyncUuid>()
            .unwrap();
        metadata.set_deck_uuid(&deck_uuid);
        let config = AppConfig {
            sync_server_url: Some("https://sync.example.invalid".to_string()),
            sync_owner_identity: Some("acct:modulus@example.invalid".to_string()),
            sync_bearer_token: Some("invalid_cosimo_token".to_string()),
            ..AppConfig::default()
        };

        let text = sync_status_text_with_remote(
            Localizer::default(),
            &metadata,
            &config,
            Some(RemoteSyncStatusOverride::AuthenticationFailed),
        );

        assert!(text.contains(
            "Upload with study history: Not available: saved sync login is no longer valid. Log in again."
        ));
        assert!(text.contains(
            "Download synced deck: Not available: saved sync login is no longer valid. Log in again."
        ));
        assert!(text.contains("Remote deck: Saved sync login is no longer valid. Log in again."));
        assert!(!text.contains("unexpected HTTP status 401"));
    }

    #[test]
    fn sync_status_marks_download_unavailable_when_remote_deck_is_not_found() {
        let mut metadata = DeckMetadata::v2();
        let deck_uuid = "550e8400-e29b-41d4-a716-446655440000"
            .parse::<SyncUuid>()
            .unwrap();
        metadata.set_deck_uuid(&deck_uuid);
        let config = AppConfig {
            sync_server_url: Some("https://sync.example.invalid".to_string()),
            ..AppConfig::default()
        };

        let text = sync_status_text_with_remote(
            Localizer::default(),
            &metadata,
            &config,
            Some(RemoteSyncStatusOverride::NotFound),
        );

        assert!(text.contains(
            "Upload with study history: Not available: log in to the sync server first."
        ));
        assert!(text.contains("Download synced deck: Not available: remote deck was not found."));
        assert!(text.contains("Remote deck: Not found"));
        assert!(!text.contains("Download synced deck: Available"));
    }

    #[test]
    fn sync_status_allows_content_download_when_owner_check_fails_but_shared_status_exists() {
        let mut metadata = DeckMetadata::v2();
        let deck_uuid = "550e8400-e29b-41d4-a716-446655440000"
            .parse::<SyncUuid>()
            .unwrap();
        metadata.set_deck_uuid(&deck_uuid);
        let shared_status = SyncRemoteDeckStatus::from_shared_status(
            SyncSharedDeckStatus::from_parts(
                deck_uuid,
                "43",
                DeckVisibility::Unlisted,
                "acct:modulus@example.invalid",
                "sha256:content",
            )
            .unwrap(),
            None,
            None,
        )
        .unwrap();
        let config = AppConfig {
            sync_server_url: Some("https://sync.example.invalid".to_string()),
            sync_owner_identity: Some("acct:modulus@example.invalid".to_string()),
            sync_bearer_token: Some("invalid_cosimo_token".to_string()),
            ..AppConfig::default()
        };

        let text = sync_status_text_with_remote(
            Localizer::default(),
            &metadata,
            &config,
            Some(RemoteSyncStatusOverride::FoundWithOwnerReadError {
                status: Box::new(shared_status),
                owner_error: "request timed out".to_string(),
            }),
        );

        assert!(text.contains(
            "Upload with study history: Not available: could not check remote sync state: request timed out"
        ));
        assert!(text.contains("Download synced deck: Available: deck content only"));
        assert!(text.contains("Remote deck: Found"));
        assert!(text.contains(
            "Remote study history: Not available: could not check remote sync state: request timed out"
        ));
        assert!(text.contains(
            "Remote last upload device: Not available: could not check remote sync state: request timed out"
        ));
    }

    #[test]
    fn sync_status_allows_content_download_when_owner_authentication_fails_but_shared_status_exists()
     {
        let mut metadata = DeckMetadata::v2();
        let deck_uuid = "550e8400-e29b-41d4-a716-446655440000"
            .parse::<SyncUuid>()
            .unwrap();
        metadata.set_deck_uuid(&deck_uuid);
        let shared_status = SyncRemoteDeckStatus::from_shared_status(
            SyncSharedDeckStatus::from_parts(
                deck_uuid,
                "43",
                DeckVisibility::Unlisted,
                "acct:modulus@example.invalid",
                "sha256:content",
            )
            .unwrap(),
            None,
            None,
        )
        .unwrap();
        let config = AppConfig {
            sync_server_url: Some("https://sync.example.invalid".to_string()),
            sync_owner_identity: Some("acct:modulus@example.invalid".to_string()),
            sync_bearer_token: Some("invalid_cosimo_token".to_string()),
            ..AppConfig::default()
        };

        let text = sync_status_text_with_remote(
            Localizer::default(),
            &metadata,
            &config,
            Some(
                RemoteSyncStatusOverride::FoundWithOwnerAuthenticationFailed {
                    status: Box::new(shared_status),
                },
            ),
        );

        assert!(text.contains(
            "Upload with study history: Not available: saved sync login is no longer valid. Log in again."
        ));
        assert!(text.contains("Download synced deck: Available: deck content only"));
        assert!(text.contains("Remote deck: Found"));
        assert!(text.contains(
            "Remote study history: Not available: saved sync login is no longer valid. Log in again."
        ));
        assert!(text.contains(
            "Remote last upload device: Not available: saved sync login is no longer valid. Log in again."
        ));
        assert!(!text.contains("unexpected HTTP status 401"));
    }

    #[test]
    fn sync_status_reports_owner_only_remote_fields_as_unavailable_for_shared_access() {
        let mut metadata = DeckMetadata::v2();
        let deck_uuid = "550e8400-e29b-41d4-a716-446655440000"
            .parse::<SyncUuid>()
            .unwrap();
        metadata.set_deck_uuid(&deck_uuid);
        let remote_status = SyncRemoteDeckStatus::from_shared_status(
            SyncSharedDeckStatus::from_parts(
                deck_uuid,
                "43",
                DeckVisibility::Public,
                "acct:other@example.invalid",
                "sha256:content",
            )
            .unwrap(),
            None,
            None,
        )
        .unwrap();
        let config = AppConfig {
            sync_server_url: Some("https://sync.example.invalid".to_string()),
            ..AppConfig::default()
        };

        let text = sync_status_text_with_remote(
            Localizer::default(),
            &metadata,
            &config,
            Some(RemoteSyncStatusOverride::Found(Box::new(remote_status))),
        );

        assert!(text.contains("Download synced deck: Available: deck content only"));
        assert!(text.contains("Remote owner: @other@example.invalid"));
        assert!(text.contains("Remote study history: Not available: owner-only information."));
        assert!(text.contains("Remote last upload device: Not available: owner-only information."));
    }

    #[test]
    fn sync_status_marks_upload_unavailable_for_remote_owner_mismatch() {
        let mut metadata = DeckMetadata::v2();
        let deck_uuid = "550e8400-e29b-41d4-a716-446655440000"
            .parse::<SyncUuid>()
            .unwrap();
        metadata.set_deck_uuid(&deck_uuid);
        let remote_status = remote_status_for_test(deck_uuid, "acct:other@example.invalid", None);
        let config = AppConfig {
            sync_server_url: Some("https://sync.example.invalid".to_string()),
            sync_owner_identity: Some("acct:current@example.invalid".to_string()),
            sync_bearer_token: Some("cosimo_token".to_string()),
            ..AppConfig::default()
        };

        let text = sync_status_text_with_remote(
            Localizer::default(),
            &metadata,
            &config,
            Some(RemoteSyncStatusOverride::Found(Box::new(remote_status))),
        );

        assert!(text.contains(
            "Upload with study history: This deck is already owned on the sync server by @other@example.invalid. Cosimo is logged in as @current@example.invalid, so it will not upload this deck."
        ));
        assert!(text.contains("Download synced deck: Available: deck content only"));
    }

    #[test]
    fn sync_status_reports_anonymous_shared_download_state_without_login() {
        let mut metadata = DeckMetadata::v2();
        let deck_uuid = "550e8400-e29b-41d4-a716-446655440000"
            .parse::<SyncUuid>()
            .unwrap();
        metadata.set_deck_uuid(&deck_uuid);
        let mut config = AppConfig {
            sync_server_url: Some("https://sync.example.invalid".to_string()),
            ..AppConfig::default()
        };
        config.deck_sync_states.push(
            LocalDeckSyncState::from_parts(
                deck_uuid,
                "https://sync.example.invalid",
                None,
                Some("17"),
                None,
                Some("sha256:shared-download"),
            )
            .unwrap(),
        );

        let text = sync_status_text(Localizer::default(), &metadata, &config);

        assert!(text.contains("Sync account: None"));
        assert!(text.contains(
            "Upload with study history: Not available: log in to the sync server first."
        ));
        assert!(text.contains("Download synced deck: Available"));
        assert!(text.contains("Last seen remote revision: 17"));
        assert!(text.contains("Last uploaded bundle hash: None"));
        assert!(text.contains("Last downloaded bundle hash: sha256:shared-download"));
    }

    #[test]
    fn sync_status_prefers_authenticated_owner_state_over_shared_download_state() {
        let mut metadata = DeckMetadata::v2();
        let deck_uuid = "550e8400-e29b-41d4-a716-446655440000"
            .parse::<SyncUuid>()
            .unwrap();
        metadata.set_deck_uuid(&deck_uuid);
        let mut config = AppConfig {
            sync_server_url: Some("https://sync.example.invalid".to_string()),
            sync_owner_identity: Some("acct:current@example.invalid".to_string()),
            sync_bearer_token: Some("cosimo_token".to_string()),
            ..AppConfig::default()
        };
        config.deck_sync_states.push(
            LocalDeckSyncState::from_parts(
                deck_uuid.clone(),
                "https://sync.example.invalid",
                None,
                Some("17"),
                None,
                Some("sha256:shared-download"),
            )
            .unwrap(),
        );
        config.deck_sync_states.push(
            LocalDeckSyncState::from_parts(
                deck_uuid,
                "https://sync.example.invalid",
                Some("acct:current@example.invalid"),
                Some("42"),
                Some("sha256:owner-upload"),
                Some("sha256:owner-download"),
            )
            .unwrap(),
        );

        let text = sync_status_text(Localizer::default(), &metadata, &config);

        assert!(text.contains("Sync account: @current@example.invalid"));
        assert!(text.contains("Last seen remote revision: 42"));
        assert!(text.contains("Last uploaded bundle hash: sha256:owner-upload"));
        assert!(text.contains("Last downloaded bundle hash: sha256:owner-download"));
        assert!(!text.contains("sha256:shared-download"));
    }

    #[test]
    fn sync_status_prefers_newest_local_state_after_logout() {
        let mut metadata = DeckMetadata::v2();
        let deck_uuid = "550e8400-e29b-41d4-a716-446655440000"
            .parse::<SyncUuid>()
            .unwrap();
        metadata.set_deck_uuid(&deck_uuid);
        let mut config = AppConfig {
            sync_server_url: Some("https://sync.example.invalid".to_string()),
            ..AppConfig::default()
        };
        config.deck_sync_states.push(
            LocalDeckSyncState::from_parts(
                deck_uuid.clone(),
                "https://sync.example.invalid",
                None,
                Some("79"),
                None,
                Some("sha256:shared-download"),
            )
            .unwrap(),
        );
        config.deck_sync_states.push(
            LocalDeckSyncState::from_parts(
                deck_uuid,
                "https://sync.example.invalid",
                Some("acct:current@example.invalid"),
                Some("80"),
                Some("sha256:owner-upload"),
                Some("sha256:owner-download"),
            )
            .unwrap(),
        );

        let text = sync_status_text(Localizer::default(), &metadata, &config);

        assert!(text.contains("Sync account: @current@example.invalid"));
        assert!(text.contains("Last seen remote revision: 80"));
        assert!(text.contains("Last uploaded bundle hash: sha256:owner-upload"));
        assert!(text.contains("Last downloaded bundle hash: sha256:owner-download"));
        assert!(!text.contains("sha256:shared-download"));
    }

    #[test]
    fn sync_status_ignores_revision_state_for_other_owners() {
        let mut metadata = DeckMetadata::v2();
        let deck_uuid = "550e8400-e29b-41d4-a716-446655440000"
            .parse::<SyncUuid>()
            .unwrap();
        metadata.set_deck_uuid(&deck_uuid);
        let mut config = AppConfig {
            sync_server_url: Some("https://sync.example.invalid".to_string()),
            sync_owner_identity: Some("acct:current@example.invalid".to_string()),
            sync_bearer_token: Some("cosimo_token".to_string()),
            ..AppConfig::default()
        };
        config.deck_sync_states.push(
            LocalDeckSyncState::from_parts(
                deck_uuid,
                "https://sync.example.invalid",
                Some("acct:other@example.invalid"),
                Some("42"),
                Some("sha256:upload"),
                Some("sha256:download"),
            )
            .unwrap(),
        );

        let text = sync_status_text(Localizer::default(), &metadata, &config);

        assert!(text.contains("Sync account: @current@example.invalid"));
        assert!(text.contains("Last seen remote revision: None"));
        assert!(text.contains("Last uploaded bundle hash: None"));
        assert!(text.contains("Last downloaded bundle hash: None"));
    }

    #[test]
    fn sync_status_ignores_legacy_local_directory_state() {
        let mut metadata = DeckMetadata::v2();
        let deck_uuid = "550e8400-e29b-41d4-a716-446655440000"
            .parse::<SyncUuid>()
            .unwrap();
        metadata.set_deck_uuid(&deck_uuid);
        let mut config = AppConfig::default();
        config.deck_sync_states.push(
            LocalDeckSyncState::from_parts(
                deck_uuid.clone(),
                "local-directory:/tmp/cosimo-sync",
                Some("acct:modulus@example.invalid"),
                Some("42"),
                Some("sha256:upload"),
                Some("sha256:download"),
            )
            .unwrap(),
        );

        let text = sync_status_text(Localizer::default(), &metadata, &config);

        assert!(text.contains("Sync account: None"));
        assert!(text.contains(
            "Upload with study history: Not available: log in to the sync server first."
        ));
    }
}
