use std::path::Path;

use cosimo::{
    HttpSyncClient, HttpSyncClientError, LocalDeckSyncState, LocalDeviceIdentity,
    SyncDownloadError, SyncDownloadKind, SyncRemoteDeckStatus, SyncSharedDeckStatus,
};

use crate::app_config::{self, CONFIG_PATH, load_app_config};
use crate::file_ops::portable_path;
use crate::localization::Localizer;
use crate::sync_identity::display_owner_identity;
use crate::sync_remote_decks_model::{RemoteDeckListEntry, remote_deck_list_entries};
use crate::sync_status_model::{RemoteSyncQuotaOverride, RemoteSyncStatusOverride};

#[derive(Debug, Clone)]
pub(crate) struct HttpSyncRuntimeSettings {
    pub(crate) server_url: String,
    pub(crate) owner_identity: String,
    pub(crate) bearer_token: Option<String>,
    pub(crate) device_identity: LocalDeviceIdentity,
}

impl HttpSyncRuntimeSettings {
    pub(crate) fn target_for_message(&self) -> String {
        self.server_url.clone()
    }
}

#[derive(Debug, Clone)]
pub(crate) struct HttpSyncRemoteDeckSettings {
    pub(crate) server_url: String,
    pub(crate) owner_identity: Option<String>,
    pub(crate) bearer_token: Option<String>,
}

impl HttpSyncRemoteDeckSettings {
    pub(crate) fn target_for_message(&self) -> String {
        self.server_url.clone()
    }
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct CurrentDeckSyncDownloadPlan {
    pub(crate) download_kind: SyncDownloadKind,
    pub(crate) requester_owner_identity: Option<String>,
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct SyncUploadConflict {
    pub(crate) local_expected_revision: Option<String>,
    pub(crate) remote_revision: Option<String>,
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) enum SyncUploadPreflight {
    Ready,
    Conflict(SyncUploadConflict),
}

#[derive(Debug, Clone)]
pub(crate) struct RemoteDecksRuntimeResult {
    pub(crate) entries: Vec<RemoteDeckListEntry>,
    pub(crate) authentication_failed: bool,
}

pub(crate) fn http_sync_runtime_settings(
    localizer: Localizer,
) -> Result<HttpSyncRuntimeSettings, String> {
    let config_path = portable_path(CONFIG_PATH);
    let config = load_app_config(&config_path)?;
    http_sync_runtime_settings_from_config(&config_path, &config, localizer)
}

pub(crate) fn http_sync_remote_deck_settings(
    localizer: Localizer,
) -> Result<HttpSyncRemoteDeckSettings, String> {
    let config = load_app_config(portable_path(CONFIG_PATH))?;
    let Some(server_url) = config.sync_server_url.as_ref() else {
        return Err(localizer.sync_settings_missing_message().to_string());
    };
    let auth = sync_owner_and_token_for_config(&config);
    Ok(HttpSyncRemoteDeckSettings {
        server_url: server_url.clone(),
        owner_identity: auth.as_ref().map(|(owner, _)| owner.clone()),
        bearer_token: auth.and_then(|(_, token)| token),
    })
}

pub(crate) fn http_sync_runtime_settings_from_config(
    _config_path: &Path,
    config: &app_config::AppConfig,
    localizer: Localizer,
) -> Result<HttpSyncRuntimeSettings, String> {
    let Some(server_url) = config.sync_server_url.as_deref() else {
        return Err(localizer.sync_settings_missing_message().to_string());
    };
    let (owner_identity, bearer_token) = sync_owner_and_token_for_config(config)
        .ok_or_else(|| localizer.sync_login_missing_message().to_string())?;
    Ok(HttpSyncRuntimeSettings {
        server_url: server_url.to_string(),
        owner_identity,
        bearer_token,
        device_identity: LocalDeviceIdentity::new(config.local_device_name.clone()),
    })
}

pub(crate) fn sync_owner_and_token_for_config(
    config: &app_config::AppConfig,
) -> Option<(String, Option<String>)> {
    let owner_identity = config.sync_owner_identity.as_deref()?;
    let bearer_token = config.sync_bearer_token.as_deref()?;
    Some((owner_identity.to_string(), Some(bearer_token.to_string())))
}

pub(crate) fn display_optional_owner_identity(owner_identity: Option<&str>) -> Option<String> {
    owner_identity.map(|identity| display_owner_identity(identity).into_owned())
}

pub(crate) fn remote_decks_for_runtime(
    settings: &HttpSyncRemoteDeckSettings,
) -> Result<RemoteDecksRuntimeResult, String> {
    let client = HttpSyncClient::new(&settings.server_url).map_err(|error| error.to_string())?;
    let public_entries = client
        .list_public_decks()
        .map_err(|error| error.to_string())?;
    let mut authentication_failed = false;
    let owner_entries = match (
        settings.owner_identity.as_deref(),
        settings.bearer_token.as_deref(),
    ) {
        (Some(owner), Some(token)) => match client.with_bearer_token(token).list_owner_decks(owner)
        {
            Ok(entries) => entries,
            Err(HttpSyncClientError::Authentication(_)) => {
                authentication_failed = true;
                Vec::new()
            }
            Err(error) => return Err(error.to_string()),
        },
        _ => Vec::new(),
    };
    Ok(RemoteDecksRuntimeResult {
        entries: remote_deck_list_entries(owner_entries, public_entries),
        authentication_failed,
    })
}

pub(crate) fn http_remote_status_for_metadata(
    metadata: &cosimo::DeckMetadata,
    config: &app_config::AppConfig,
) -> Option<RemoteSyncStatusOverride> {
    let deck_uuid = metadata.deck_uuid()?;
    let server_url = config.sync_server_url.as_deref()?;
    if let Some((owner_identity, bearer_token)) = sync_owner_and_token_for_config(config) {
        let owner_result = HttpSyncClient::new(server_url)
            .map(|client| match bearer_token.as_deref() {
                Some(token) => client.with_bearer_token(token),
                None => client,
            })
            .and_then(|client| client.list_owner_decks(&owner_identity))
            .map(|entries| {
                entries
                    .into_iter()
                    .find(|status| status.deck_uuid() == &deck_uuid)
            });
        match owner_result {
            Ok(Some(status)) => return Some(RemoteSyncStatusOverride::Found(Box::new(status))),
            Ok(None) => {}
            Err(error) => {
                let shared_status = remote_status_from_shared_result(
                    HttpSyncClient::new(server_url)
                        .and_then(|client| client.inspect_shared_deck(&deck_uuid)),
                );
                if matches!(error, HttpSyncClientError::Authentication(_)) {
                    return Some(remote_status_after_owner_authentication_failure(
                        shared_status,
                    ));
                }
                return Some(remote_status_after_owner_read_error(
                    error.to_string(),
                    shared_status,
                ));
            }
        }
    }

    Some(remote_status_from_shared_result(
        HttpSyncClient::new(server_url).and_then(|client| client.inspect_shared_deck(&deck_uuid)),
    ))
}

pub(crate) fn remote_status_from_shared_result(
    result: Result<SyncSharedDeckStatus, HttpSyncClientError>,
) -> RemoteSyncStatusOverride {
    match result {
        Ok(shared_status) => SyncRemoteDeckStatus::from_shared_status(shared_status, None, None)
            .map(|status| RemoteSyncStatusOverride::Found(Box::new(status)))
            .unwrap_or_else(|error| RemoteSyncStatusOverride::ReadError(error.to_string())),
        Err(HttpSyncClientError::Download(SyncDownloadError::NotFound)) => {
            RemoteSyncStatusOverride::NotFound
        }
        Err(HttpSyncClientError::Authentication(_)) => {
            RemoteSyncStatusOverride::AuthenticationFailed
        }
        Err(error) => RemoteSyncStatusOverride::ReadError(error.to_string()),
    }
}

pub(crate) fn remote_status_after_owner_authentication_failure(
    shared_status: RemoteSyncStatusOverride,
) -> RemoteSyncStatusOverride {
    match shared_status {
        RemoteSyncStatusOverride::Found(status) => {
            RemoteSyncStatusOverride::FoundWithOwnerAuthenticationFailed { status }
        }
        _ => RemoteSyncStatusOverride::AuthenticationFailed,
    }
}

pub(crate) fn remote_status_after_owner_read_error(
    owner_error: String,
    shared_status: RemoteSyncStatusOverride,
) -> RemoteSyncStatusOverride {
    match shared_status {
        RemoteSyncStatusOverride::Found(status) => {
            RemoteSyncStatusOverride::FoundWithOwnerReadError {
                status,
                owner_error,
            }
        }
        _ => RemoteSyncStatusOverride::ReadError(owner_error),
    }
}

pub(crate) fn http_quota_status_for_config(
    config: &app_config::AppConfig,
) -> Option<RemoteSyncQuotaOverride> {
    let server_url = config.sync_server_url.as_deref()?;
    let result = HttpSyncClient::new(server_url)
        .map(|client| match config.sync_bearer_token.as_deref() {
            Some(token) => client.with_bearer_token(token),
            None => client,
        })
        .and_then(|client| client.quota_status());
    Some(match result {
        Ok(status) => RemoteSyncQuotaOverride::Found(status),
        Err(HttpSyncClientError::Authentication(_)) => {
            RemoteSyncQuotaOverride::AuthenticationFailed
        }
        Err(error) => RemoteSyncQuotaOverride::ReadError(error.to_string()),
    })
}

pub(crate) fn current_deck_sync_download_plan(
    localizer: Localizer,
    remote_status: Option<RemoteSyncStatusOverride>,
    owner_identity: Option<&str>,
) -> Result<CurrentDeckSyncDownloadPlan, String> {
    let remote_status = remote_status.ok_or_else(|| localizer.sync_settings_missing_message())?;
    match remote_status {
        RemoteSyncStatusOverride::Found(status)
            if owner_identity == Some(status.owner_identity()) =>
        {
            Ok(CurrentDeckSyncDownloadPlan {
                download_kind: SyncDownloadKind::OwnerStudyHistory,
                requester_owner_identity: owner_identity.map(ToString::to_string),
            })
        }
        RemoteSyncStatusOverride::Found(_) => Ok(CurrentDeckSyncDownloadPlan {
            download_kind: SyncDownloadKind::SharedContent,
            requester_owner_identity: None,
        }),
        RemoteSyncStatusOverride::FoundWithOwnerReadError { .. } => {
            Ok(CurrentDeckSyncDownloadPlan {
                download_kind: SyncDownloadKind::SharedContent,
                requester_owner_identity: None,
            })
        }
        RemoteSyncStatusOverride::FoundWithOwnerAuthenticationFailed { .. } => {
            Ok(CurrentDeckSyncDownloadPlan {
                download_kind: SyncDownloadKind::SharedContent,
                requester_owner_identity: None,
            })
        }
        RemoteSyncStatusOverride::NotFound => Err(localizer.sync_status_not_found().to_string()),
        RemoteSyncStatusOverride::AuthenticationFailed => {
            Err(localizer.sync_status_authentication_failed().to_string())
        }
        RemoteSyncStatusOverride::ReadError(error) => {
            Err(localizer.sync_status_remote_read_error(&error))
        }
    }
}

pub(crate) fn local_sync_revision_for_upload(
    config: &app_config::AppConfig,
    deck_uuid: &cosimo::SyncUuid,
    server_url: &str,
    owner_identity: &str,
) -> Option<String> {
    let probe = LocalDeckSyncState::from_parts(
        deck_uuid.clone(),
        server_url,
        Some(owner_identity),
        None,
        None,
        None,
    )
    .ok()?;
    config
        .deck_sync_states
        .iter()
        .find(|state| state.same_remote_slot(&probe))
        .and_then(LocalDeckSyncState::last_seen_remote_revision)
        .map(ToString::to_string)
}

pub(crate) fn sync_upload_preflight_for_remote_owner(
    localizer: Localizer,
    remote_status: Option<RemoteSyncStatusOverride>,
    local_expected_revision: Option<&str>,
    owner_identity: &str,
) -> Result<SyncUploadPreflight, String> {
    let Some(remote_status) = remote_status else {
        return Ok(SyncUploadPreflight::Ready);
    };
    match remote_status {
        RemoteSyncStatusOverride::Found(status) if status.owner_identity() != owner_identity => {
            Err(localizer.sync_upload_owner_mismatch_message(
                &display_owner_identity(status.owner_identity()),
                &display_owner_identity(owner_identity),
            ))
        }
        RemoteSyncStatusOverride::Found(status) => {
            if local_expected_revision == Some(status.revision()) {
                Ok(SyncUploadPreflight::Ready)
            } else {
                Ok(SyncUploadPreflight::Conflict(SyncUploadConflict {
                    local_expected_revision: local_expected_revision.map(ToString::to_string),
                    remote_revision: Some(status.revision().to_string()),
                }))
            }
        }
        RemoteSyncStatusOverride::FoundWithOwnerReadError { owner_error, .. } => {
            Err(localizer.sync_status_remote_read_error(&owner_error))
        }
        RemoteSyncStatusOverride::FoundWithOwnerAuthenticationFailed { .. }
        | RemoteSyncStatusOverride::AuthenticationFailed => {
            Err(localizer.sync_status_authentication_failed().to_string())
        }
        RemoteSyncStatusOverride::NotFound => Ok(SyncUploadPreflight::Ready),
        RemoteSyncStatusOverride::ReadError(error) => {
            Err(localizer.sync_status_remote_read_error(&error))
        }
    }
}
