use std::fmt;

use busylib::model::updater::{
    AutoupdateSettings, CheckEvent, CheckResult, CheckStatus, DownloadProgress, InstallResult,
    InstallStatus, UpdateAction, UpdateEvent, UpdateStatus,
};
use serde::Serialize;

use crate::reporter::events::CliEvent;
use crate::reporter::events::fields::{Field, field, prefixed, write_fields};

#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
pub struct UpdaterStatusEvent(UpdateStatus);

#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
pub struct UpdaterChangelogEvent {
    changelog: String,
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
pub struct UpdaterAutoupdateEvent(AutoupdateSettings);

impl UpdaterStatusEvent {
    pub fn new(status: UpdateStatus) -> Self {
        Self(status)
    }
}

impl UpdaterChangelogEvent {
    pub fn new(changelog: String) -> Self {
        Self { changelog }
    }
}

impl UpdaterAutoupdateEvent {
    pub fn new(settings: AutoupdateSettings) -> Self {
        Self(settings)
    }
}

impl From<UpdaterStatusEvent> for CliEvent {
    fn from(event: UpdaterStatusEvent) -> Self {
        CliEvent::UpdaterStatus(Box::new(event))
    }
}

impl From<UpdaterChangelogEvent> for CliEvent {
    fn from(event: UpdaterChangelogEvent) -> Self {
        CliEvent::UpdaterChangelog(event)
    }
}

impl From<UpdaterAutoupdateEvent> for CliEvent {
    fn from(event: UpdaterAutoupdateEvent) -> Self {
        CliEvent::UpdaterAutoupdate(event)
    }
}

impl fmt::Display for UpdaterStatusEvent {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        let status = &self.0;
        let mut fields = Vec::new();

        if let Some(install) = &status.install {
            fields.extend(prefixed("install", install_fields(install)));
        }

        if let Some(check) = &status.check {
            fields.extend(prefixed("check", check_fields(check)));
        }

        write_fields(f, &fields)
    }
}

impl fmt::Display for UpdaterChangelogEvent {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.write_str(&self.changelog)
    }
}

impl fmt::Display for UpdaterAutoupdateEvent {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        let settings = &self.0;
        let mut fields = Vec::new();

        if let Some(is_enabled) = settings.is_enabled {
            fields.push(field("enabled", is_enabled));
        }

        if let Some(interval_start) = &settings.interval_start {
            fields.push(field("interval start", interval_start));
        }

        if let Some(interval_end) = &settings.interval_end {
            fields.push(field("interval end", interval_end));
        }

        write_fields(f, &fields)
    }
}

fn install_fields(install: &InstallStatus) -> Vec<Field> {
    let mut fields = Vec::new();

    if let Some(is_allowed) = install.is_allowed {
        fields.push(field("allowed", is_allowed));
    }

    if let Some(event) = &install.event {
        fields.push(field("event", update_event_label(event)));
    }

    if let Some(action) = &install.action {
        fields.push(field("action", update_action_label(action)));
    }

    if let Some(status) = &install.status {
        fields.push(field("status", install_result_label(status)));
    }

    if let Some(detail) = &install.detail
        && !detail.is_empty()
    {
        fields.push(field("detail", detail));
    }

    if let Some(download) = &install.download {
        fields.extend(prefixed("download", download_fields(download)));
    }

    fields
}

fn download_fields(download: &DownloadProgress) -> Vec<Field> {
    let mut fields = Vec::new();

    if let Some(speed) = download.speed_bytes_per_sec {
        fields.push(field("speed bytes per sec", speed));
    }

    if let Some(received) = download.received_bytes {
        fields.push(field("received bytes", received));
    }

    if let Some(total) = download.total_bytes {
        fields.push(field("total bytes", total));
    }

    fields
}

fn check_fields(check: &CheckStatus) -> Vec<Field> {
    let mut fields = Vec::new();

    if let Some(version) = check.available_version() {
        fields.push(field("available version", version));
    }

    if let Some(event) = &check.event {
        fields.push(field("event", check_event_label(event)));
    }

    if let Some(status) = &check.status {
        fields.push(field("status", check_result_label(status)));
    }

    fields
}

fn update_event_label(event: &UpdateEvent) -> &str {
    match event {
        UpdateEvent::SessionStart => "session_start",
        UpdateEvent::SessionStop => "session_stop",
        UpdateEvent::ActionBegin => "action_begin",
        UpdateEvent::ActionDone => "action_done",
        UpdateEvent::DetailChange => "detail_change",
        UpdateEvent::ActionProgress => "action_progress",
        UpdateEvent::None => "none",
        UpdateEvent::Unknown(event) => event,
    }
}

fn update_action_label(action: &UpdateAction) -> &str {
    match action {
        UpdateAction::Download => "download",
        UpdateAction::ShaVerification => "sha_verification",
        UpdateAction::Unpack => "unpack",
        UpdateAction::Prepare => "prepare",
        UpdateAction::Apply => "apply",
        UpdateAction::None => "none",
        UpdateAction::Unknown(action) => action,
    }
}

fn install_result_label(status: &InstallResult) -> &str {
    match status {
        InstallResult::Ok => "ok",
        InstallResult::BatteryLow => "battery_low",
        InstallResult::Busy => "busy",
        InstallResult::DownloadFailure => "download_failure",
        InstallResult::DownloadAbort => "download_abort",
        InstallResult::ShaMismatch => "sha_mismatch",
        InstallResult::UnpackStagingDirFailure => "unpack_staging_dir_failure",
        InstallResult::UnpackArchiveOpenFailure => "unpack_archive_open_failure",
        InstallResult::UnpackArchiveUnpackFailure => "unpack_archive_unpack_failure",
        InstallResult::InstallManifestNotFound => "install_manifest_not_found",
        InstallResult::InstallManifestInvalid => "install_manifest_invalid",
        InstallResult::InstallSessionConfigFailure => "install_session_config_failure",
        InstallResult::InstallPointerSetupFailure => "install_pointer_setup_failure",
        InstallResult::UnknownFailure => "unknown_failure",
        InstallResult::Unknown(status) => status,
    }
}

fn check_event_label(event: &CheckEvent) -> &str {
    match event {
        CheckEvent::Start => "start",
        CheckEvent::Stop => "stop",
        CheckEvent::None => "none",
        CheckEvent::Unknown(event) => event,
    }
}

fn check_result_label(status: &CheckResult) -> &str {
    match status {
        CheckResult::Available => "available",
        CheckResult::NotAvailable => "not_available",
        CheckResult::Failure => "failure",
        CheckResult::None => "none",
        CheckResult::Unknown(status) => status,
    }
}

#[cfg(test)]
mod tests {
    use busylib::types::time_of_day::TimeOfDay;

    use super::*;

    #[test]
    fn prefixes_the_install_and_check_sections() {
        let event = UpdaterStatusEvent::new(UpdateStatus {
            install: Some(InstallStatus {
                is_allowed: Some(true),
                event: Some(UpdateEvent::None),
                action: Some(UpdateAction::None),
                status: Some(InstallResult::Ok),
                detail: Some(String::new()),
                download: None,
            }),
            check: Some(CheckStatus {
                available_version: Some(String::new()),
                event: Some(CheckEvent::None),
                status: Some(CheckResult::NotAvailable),
            }),
        });

        assert_eq!(
            event.to_string(),
            "install allowed: true\n\
             install event: none\n\
             install action: none\n\
             install status: ok\n\
             check event: none\n\
             check status: not_available"
        );
    }

    #[test]
    fn renders_the_autoupdate_window() {
        let event = UpdaterAutoupdateEvent::new(AutoupdateSettings {
            is_enabled: Some(true),
            interval_start: Some(TimeOfDay::new("02:00").unwrap()),
            interval_end: Some(TimeOfDay::new("05:00").unwrap()),
        });

        assert_eq!(
            event.to_string(),
            "enabled: true\ninterval start: 02:00\ninterval end: 05:00"
        );
    }
}