//! Attended-mode consent prompt (Task 5).
//!
//! For an attended (support-code) session, the GuruConnect server sends the
//! agent a `ConsentRequest` before the technician's session is allowed to go
//! live. The agent shows the end user a native dialog ("Allow <technician> to
//! VIEW/CONTROL this computer?") and returns the user's choice as a
//! `ConsentResponse`. The server holds the session in `consent_state = pending`
//! and tears it down on a denial or timeout.
//!
//! v1 uses a Windows `MessageBox` (Yes/No, top-most, foreground). It is
//! synchronous and reliable on every supported Windows version (7 SP1+), needs
//! no extra windowing, and cannot be dismissed into an ambiguous state — the
//! only outcomes are Yes (allow), No (deny), or the box being closed (treated
//! as deny). A nicer custom branded dialog (countdown, technician avatar) is a
//! possible future refinement; it is not required for correctness.
//!
//! The decision is the end user's and is purely advisory to the agent: the
//! server is the enforcement point (it will not surface the session to the
//! technician until it receives a `granted` response). The agent simply relays
//! the human's choice.

/// Whether the technician requested view-only or full control, used only to
/// phrase the prompt. Mirrors `proto::ConsentAccessMode`.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ConsentAccessMode {
    View,
    Control,
}

impl ConsentAccessMode {
    /// Decode the proto enum value (defaults to the more conservative `Control`
    /// wording on an unknown value so the prompt never under-states access).
    pub fn from_proto(value: i32) -> Self {
        match crate::proto::ConsentAccessMode::try_from(value) {
            Ok(crate::proto::ConsentAccessMode::ConsentView) => ConsentAccessMode::View,
            Ok(crate::proto::ConsentAccessMode::ConsentControl) => ConsentAccessMode::Control,
            Err(_) => ConsentAccessMode::Control,
        }
    }

    fn verb(self) -> &'static str {
        match self {
            ConsentAccessMode::View => "VIEW",
            ConsentAccessMode::Control => "VIEW and CONTROL",
        }
    }
}

/// Build the consent prompt body shown to the end user.
fn prompt_body(technician_name: &str, access: ConsentAccessMode) -> String {
    let who = if technician_name.trim().is_empty() {
        "A support technician"
    } else {
        technician_name
    };
    format!(
        "{who} is requesting a remote support session.\n\n\
         If you allow this, they will be able to {verb} this computer.\n\n\
         Do you want to allow this remote support session?",
        who = who,
        verb = access.verb()
    )
}

/// Show the consent dialog and return the end user's decision.
///
/// Returns `true` if the user ALLOWED the session, `false` if they denied it or
/// the dialog was closed/could not be shown. Blocking — callers should run this
/// off the async runtime (e.g. `tokio::task::spawn_blocking`).
#[cfg(windows)]
pub fn prompt_consent(technician_name: &str, access: ConsentAccessMode) -> bool {
    use std::ffi::OsStr;
    use std::os::windows::ffi::OsStrExt;
    use windows::core::PCWSTR;
    use windows::Win32::UI::WindowsAndMessaging::{
        MessageBoxW, IDYES, MB_ICONQUESTION, MB_SETFOREGROUND, MB_SYSTEMMODAL, MB_TOPMOST, MB_YESNO,
    };

    let title = "GuruConnect - Remote Support Request";
    let body = prompt_body(technician_name, access);

    let title_wide: Vec<u16> = OsStr::new(title)
        .encode_wide()
        .chain(std::iter::once(0))
        .collect();
    let body_wide: Vec<u16> = OsStr::new(&body)
        .encode_wide()
        .chain(std::iter::once(0))
        .collect();

    // MB_YESNO          - explicit Allow (Yes) / Deny (No)
    // MB_ICONQUESTION   - prompt styling
    // MB_TOPMOST        - sit above other windows so it cannot be hidden
    // MB_SETFOREGROUND  - bring to the foreground
    // MB_SYSTEMMODAL    - ensure visibility even from a service/elevated context
    let result = unsafe {
        MessageBoxW(
            None,
            PCWSTR(body_wide.as_ptr()),
            PCWSTR(title_wide.as_ptr()),
            MB_YESNO | MB_ICONQUESTION | MB_TOPMOST | MB_SETFOREGROUND | MB_SYSTEMMODAL,
        )
    };

    // Any outcome other than an explicit "Yes" is a denial (including the box
    // being closed, which returns IDNO/IDCANCEL-style values).
    result == IDYES
}

/// Non-Windows stub. The agent is Windows-first; on other platforms there is no
/// native end-user consent surface yet, so we fail CLOSED (deny) rather than
/// silently allowing an unattended session.
///
// TODO(platform): provide a real consent dialog on macOS/Linux when the agent
// is ported there (e.g. a GTK/Cocoa modal). Until then, deny so a non-Windows
// build can never grant an attended session without an explicit human prompt.
#[cfg(not(windows))]
pub fn prompt_consent(_technician_name: &str, _access: ConsentAccessMode) -> bool {
    tracing::warn!(
        "Consent prompt requested on a non-Windows build; no native dialog available — denying"
    );
    false
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn prompt_body_uses_control_wording() {
        let body = prompt_body("Mike", ConsentAccessMode::Control);
        assert!(body.contains("Mike"));
        assert!(body.contains("VIEW and CONTROL"));
    }

    #[test]
    fn prompt_body_uses_view_wording() {
        let body = prompt_body("Mike", ConsentAccessMode::View);
        assert!(body.contains("VIEW"));
        assert!(!body.contains("CONTROL"));
    }

    #[test]
    fn prompt_body_falls_back_on_empty_name() {
        let body = prompt_body("   ", ConsentAccessMode::Control);
        assert!(body.contains("A support technician"));
    }

    #[test]
    fn access_mode_from_proto_defaults_to_control() {
        // An out-of-range proto value must not under-state access.
        assert_eq!(
            ConsentAccessMode::from_proto(999),
            ConsentAccessMode::Control
        );
    }
}
