//! v6.0 c2 — Android `DeviceControl` impl backed by `smix_adb::AdbClient`.
//!
//! Mirror of [`crate::IosDeviceControl`]. Wraps `adb` shell commands;
//! routes `Permission` cross-platform enum to `android.permission.*`
//! grant/revoke via `pm`.
//!
//! Real-device acceptance gated behind v6.0 c3 (Kotlin runner + emulator
//! bring-up). c2 ships the trait impl skeleton + smoke tests; live
//! `adb` calls work today but no fixture installs the smix runner apk yet.

use async_trait::async_trait;
use std::path::Path;
use tokio::sync::Mutex;

use smix_adb::{AdbClient, AdbError};
use smix_driver::Platform;
use smix_simctl::SimctlError;

use crate::PermissionAction;
use crate::device_control::{DeviceControl, Permission};

/// Android `DeviceControl` impl. Wraps `smix_adb::AdbClient` + holds
/// active recording handle internally.
///
/// **State** — v6.0 c2 ships skeleton, not all methods are end-to-end
/// validated yet (acceptance gated by v6.0 c3 runner skeleton). Methods
/// covering operations not yet implemented surface `SimctlError::NonZeroExit`
/// with a clear message; `smix-adb` translation lives in `adb_to_simctl_err`.
pub struct AndroidDeviceControl {
    client: AdbClient,
    /// Active `adb shell screenrecord` child handle (PID), if recording.
    /// v6.0 c2 — placeholder; real recording wiring lands when fixture
    /// dogfood needs Android video capture.
    #[allow(dead_code)]
    recording: Mutex<Option<u32>>,
}

impl Default for AndroidDeviceControl {
    fn default() -> Self {
        Self::new()
    }
}

impl AndroidDeviceControl {
    #[must_use]
    pub fn new() -> Self {
        AndroidDeviceControl {
            client: AdbClient::new(),
            recording: Mutex::new(None),
        }
    }

    /// Construct with an existing `AdbClient` (tests / non-PATH adb).
    #[must_use]
    pub fn with_client(client: AdbClient) -> Self {
        AndroidDeviceControl {
            client,
            recording: Mutex::new(None),
        }
    }
}

/// Translate `AdbError` → `SimctlError` so the trait surface stays
/// consistent (single error type across platforms — App layer maps to
/// `ExpectationFailure`). Android-specific detail preserved in message.
fn adb_to_simctl_err(e: AdbError, subcommand: &str) -> SimctlError {
    match e {
        AdbError::BinaryNotFound => SimctlError::NonZeroExit {
            subcommand: subcommand.into(),
            code: -1,
            stderr: "adb binary not found in PATH; install Android SDK platform-tools".into(),
        },
        AdbError::Spawn(io) => SimctlError::NonZeroExit {
            subcommand: subcommand.into(),
            code: -1,
            stderr: format!("adb spawn failed: {io}"),
        },
        AdbError::NonZeroExit {
            subcommand: sub,
            code,
            stderr,
            serial,
        } => SimctlError::NonZeroExit {
            subcommand: format!("adb {sub} (serial={serial:?})"),
            code,
            stderr,
        },
        AdbError::Malformed {
            subcommand: sub,
            detail,
        } => SimctlError::Malformed {
            subcommand: sub,
            detail,
        },
    }
}

#[async_trait]
impl DeviceControl for AndroidDeviceControl {
    fn platform(&self) -> Platform {
        Platform::Android
    }

    // === Lifecycle ===

    async fn launch(&self, serial: &str, bundle_id: &str) -> Result<u32, SimctlError> {
        // v6.0 c2 / fix v6.1 c1 — AdbClient::start_activity itself builds
        // `package/activity` for `am start -n`. Pass the activity name
        // RELATIVE to the package (".MainActivity") so the wire ends up
        // `dev.smix.fixture/.MainActivity`, not the double-prefix bug
        // `dev.smix.fixture/dev.smix.fixture/.MainActivity` shipped in c2.
        self.client
            .start_activity(serial, bundle_id, ".MainActivity", &[])
            .await
            .map_err(|e| adb_to_simctl_err(e, "shell am start"))?;
        Ok(0)
    }

    async fn launch_with_args(
        &self,
        serial: &str,
        bundle_id: &str,
        args: &[String],
    ) -> Result<u32, SimctlError> {
        let extras: Vec<(String, String)> = args
            .chunks(2)
            .filter_map(|chunk| {
                if chunk.len() == 2 {
                    Some((chunk[0].clone(), chunk[1].clone()))
                } else {
                    None
                }
            })
            .collect();
        let extra_refs: Vec<(&str, &str)> = extras
            .iter()
            .map(|(k, v)| (k.as_str(), v.as_str()))
            .collect();
        self.client
            .start_activity(serial, bundle_id, ".MainActivity", &extra_refs)
            .await
            .map_err(|e| adb_to_simctl_err(e, "shell am start"))?;
        Ok(0)
    }

    async fn terminate(&self, serial: &str, bundle_id: &str) -> Result<(), SimctlError> {
        self.client
            .force_stop(serial, bundle_id)
            .await
            .map_err(|e| adb_to_simctl_err(e, "shell am force-stop"))
    }

    async fn install(&self, serial: &str, app_path: &str) -> Result<(), SimctlError> {
        self.client
            .install(serial, Path::new(app_path))
            .await
            .map_err(|e| adb_to_simctl_err(e, "install"))
    }

    async fn uninstall(&self, serial: &str, bundle_id: &str) -> Result<(), SimctlError> {
        self.client
            .uninstall(serial, bundle_id)
            .await
            .map_err(|e| adb_to_simctl_err(e, "uninstall"))
    }

    async fn keychain_reset(&self, _udid: &str) -> Result<(), SimctlError> {
        // No direct Android analog; KeyChain/AccountManager require app-side
        // intent or root. v6.x: surface as platform-no-op (matches cross-
        // platform yaml `clearKeychain: true` expectation that Android
        // silently no-ops vs. crashes).
        Ok(())
    }

    // === Lifecycle ancillary ===

    async fn open_url(&self, serial: &str, url: &str) -> Result<(), SimctlError> {
        self.client
            .shell(
                serial,
                &["am", "start", "-a", "android.intent.action.VIEW", "-d", url],
            )
            .await
            .map(|_| ())
            .map_err(|e| adb_to_simctl_err(e, "shell am start -a VIEW"))
    }

    async fn send_push(
        &self,
        _serial: &str,
        _bundle_id: &str,
        _apns_json_path: &str,
    ) -> Result<(), SimctlError> {
        // Android push = FCM not APNs. Cross-platform yaml `sendPush:` on
        // Android requires FCM credentials + Firebase project setup —
        // earned defer (v6.x R-line). Surface explicit error so yaml
        // authors know it's not silently no-op.
        Err(SimctlError::NonZeroExit {
            subcommand: "send_push".into(),
            code: -1,
            stderr: "Android FCM push not implemented (v6.x earned defer; cross-platform yaml `sendPush:` is iOS APNs only)".into(),
        })
    }

    async fn screenshot(&self, serial: &str) -> Result<Vec<u8>, SimctlError> {
        self.client
            .screenshot(serial)
            .await
            .map_err(|e| adb_to_simctl_err(e, "shell screencap"))
    }

    // === Clipboard / Media / Location ===

    async fn pasteboard_set(&self, _serial: &str, _text: &str) -> Result<(), SimctlError> {
        // Android clipboard via `cmd clipboard set-text` (API 29+) or
        // `service call clipboard` (older). v6.x dedicated cycle; skeleton
        // returns error to avoid silent failure.
        Err(SimctlError::NonZeroExit {
            subcommand: "pasteboard_set".into(),
            code: -1,
            stderr: "Android clipboard set wiring deferred to v6.x cycle".into(),
        })
    }

    async fn pasteboard_get(&self, _serial: &str) -> Result<String, SimctlError> {
        Err(SimctlError::NonZeroExit {
            subcommand: "pasteboard_get".into(),
            code: -1,
            stderr: "Android clipboard get wiring deferred to v6.x cycle".into(),
        })
    }

    async fn add_media(&self, _serial: &str, _paths: &[String]) -> Result<(), SimctlError> {
        // `adb push <local> /sdcard/Pictures/` then `am broadcast -a
        // android.intent.action.MEDIA_SCANNER_SCAN_FILE` — v6.x.
        Err(SimctlError::NonZeroExit {
            subcommand: "add_media".into(),
            code: -1,
            stderr: "Android media library push deferred to v6.x cycle".into(),
        })
    }

    async fn location_set(&self, serial: &str, lat: f64, lon: f64) -> Result<(), SimctlError> {
        // `adb emu geo fix <lon> <lat>` — emu console (note arg order:
        // lon first, then lat).
        self.client
            .shell(
                serial,
                &["emu", "geo", "fix", &lon.to_string(), &lat.to_string()],
            )
            .await
            .map(|_| ())
            .map_err(|e| adb_to_simctl_err(e, "emu geo fix"))
    }

    async fn location_start(
        &self,
        _serial: &str,
        _points: &[(f64, f64)],
        _speed_mps: Option<f64>,
    ) -> Result<(), SimctlError> {
        // Multi-waypoint route requires Android emulator GPX/KML upload via
        // emu console `geo gpx`. v6.x.
        Err(SimctlError::NonZeroExit {
            subcommand: "location_start".into(),
            code: -1,
            stderr: "Android multi-waypoint route deferred to v6.x cycle".into(),
        })
    }

    // === Permissions ===

    async fn set_permission(
        &self,
        serial: &str,
        bundle_id: &str,
        permission: Permission,
        action: PermissionAction,
    ) -> Result<(), SimctlError> {
        let Some(android_perm) = permission.to_android() else {
            // iOS-only permission on Android → no-op (cross-platform yaml
            // friendly; matches IosDeviceControl behavior for Storage on iOS).
            return Ok(());
        };
        match action {
            PermissionAction::Grant => self
                .client
                .pm_grant(serial, bundle_id, android_perm)
                .await
                .map_err(|e| adb_to_simctl_err(e, "shell pm grant")),
            PermissionAction::Revoke => self
                .client
                .pm_revoke(serial, bundle_id, android_perm)
                .await
                .map_err(|e| adb_to_simctl_err(e, "shell pm revoke")),
            PermissionAction::Reset => {
                // Android has no direct `pm reset <perm>` per-package; reset
                // = uninstall+reinstall OR `pm reset-permissions`. For
                // cross-platform yaml `permissions: { camera: unset }`
                // semantic, treat as revoke (closest match).
                self.client
                    .pm_revoke(serial, bundle_id, android_perm)
                    .await
                    .map_err(|e| adb_to_simctl_err(e, "shell pm revoke (Reset alias)"))
            }
        }
    }

    // === Recording ===

    async fn start_recording(&self, _serial: &str, _output_path: &Path) -> Result<(), SimctlError> {
        // `adb shell screenrecord /sdcard/sim.mp4` — runs on device, max
        // 3min default. v6.x dedicated cycle.
        Err(SimctlError::NonZeroExit {
            subcommand: "start_recording".into(),
            code: -1,
            stderr: "Android screenrecord wiring deferred to v6.x cycle".into(),
        })
    }

    async fn stop_recording(&self) -> Result<(), SimctlError> {
        Err(SimctlError::NonZeroExit {
            subcommand: "stop_recording".into(),
            code: -1,
            stderr: "Android screenrecord wiring deferred to v6.x cycle".into(),
        })
    }
}
