use std::sync::mpsc::Receiver;
use std::time::{Duration, Instant};

use ltk::Element;

use crate::message::Msg;
use crate::state::{Mode, State};

/// Wall-clock duration of a full slide (idle panel from covering to fully
/// off-screen, or back). Both the reveal and the return animate at this rate.
const REVEAL_SECS: f32 = 0.28;

pub struct LockscreenApp {
    state: State,
    current_user: String,
    last_tick: Instant,
    auth_rx: Option<Receiver<Result<(), String>>>,
    fingerprint: crate::auth::FingerprintListener,
    exit: bool,
    focus_request: Option<ltk::WidgetId>,
    pending_focus: bool,
    /// Target `drag_progress` of the in-flight slide: `Some(1.0)` revealing,
    /// `Some(0.0)` returning to idle, `None` when settled.
    anim_target: Option<f32>,
    anim_last: Instant,
}

impl LockscreenApp {
    pub fn new(state: State, current_user: String) -> Self {
        Self {
            state,
            current_user,
            last_tick: Instant::now() - Duration::from_secs(1),
            auth_rx: None,
            fingerprint: crate::auth::FingerprintListener::start(),
            exit: false,
            focus_request: None,
            pending_focus: false,
            anim_target: None,
            anim_last: Instant::now(),
        }
    }

    fn start_auth(&mut self) {
        if self.state.authenticating {
            return;
        }
        let mode = self.state.mode;
        let user = if mode == Mode::Greeter {
            self.state.username.clone()
        } else {
            self.current_user.clone()
        };
        if user.is_empty() {
            return;
        }
        let pass = self.state.secret.clone();

        let (tx, rx) = std::sync::mpsc::channel();
        std::thread::Builder::new()
            .name("lockscreen-auth".into())
            .spawn(move || {
                let r = match mode {
                    Mode::Greeter => crate::auth::daemon::login(&user, &pass),
                    Mode::Lock => crate::auth::verify(&user, &pass),
                };
                let _ = tx.send(r);
            })
            .ok();

        self.auth_rx = Some(rx);
        self.state.authenticating = true;
        self.state.error = None;
    }

}

impl ltk::App for LockscreenApp {
    type Message = Msg;

    fn view(&self) -> Element<Msg> {
        crate::view::view(&self.state)
    }

    fn subsurfaces(&self) -> Vec<ltk::SubsurfaceSpec<Msg>> {
        if self.state.revealed {
            return Vec::new();
        }
        let h = self.state.surface_h as f32;
        let y = -((self.state.drag_progress.clamp(0.0, 1.0) * h) as i32);
        vec![ltk::SubsurfaceSpec {
            id: ltk::SubsurfaceId(1),
            view: crate::view::idle_subsurface(&self.state),
            x: 0,
            y,
            content_version: (self.state.now.timestamp() / 60) as u64,
        }]
    }

    fn update(&mut self, msg: Msg) {
        match msg {
            Msg::SecretChanged(s) => {
                self.state.secret = s;
                self.state.error = None;
            }
            Msg::UsernameChanged(u) => {
                self.state.username = u;
                self.state.error = None;
            }
            Msg::MethodSelected(m) => {
                self.state.method = m;
                self.state.secret.clear();
                self.state.error = None;
            }
            Msg::Clear => self.state.secret.clear(),
            Msg::Submit => self.start_auth(),
            Msg::ShutdownPressed => {
                let _ = crate::auth::daemon::power_off();
            }
            Msg::RebootPressed => {
                let _ = crate::auth::daemon::reboot();
            }
            Msg::Tick => self.state.now = chrono::Local::now(),
            Msg::FingerprintMatched => {}
            Msg::Reveal => {
                // Animate the idle panel the rest of the way off-screen, then
                // reveal. Used by both the swipe (from its release progress)
                // and Enter (from 0), so both paths slide.
                if !self.state.revealed {
                    self.anim_target = Some(1.0);
                    self.anim_last = Instant::now();
                }
            }
            Msg::ReturnToIdle => {
                // Slide the idle panel back down over the form. revealed=false
                // brings the subsurface back at its current drag_progress (~1.0
                // when revealed); the return animation drives it to 0 and clears
                // the secret on arrival. Reused by ESC and any timed return.
                self.state.revealed = false;
                self.anim_target = Some(0.0);
                self.anim_last = Instant::now();
            }
            Msg::AuthResult(Ok(())) => {
                // Greeter: the daemon spawns the session and switches VT. Lock:
                // the runtime calls unlock-then-exit on the ext-session-lock
                // surface. Either way this surface's job is done.
                if self.state.mode == Mode::Greeter {
                    crate::config::save_last_user(&self.state.username);
                }
                self.exit = true;
            }
            Msg::AuthResult(Err(e)) => {
                tracing::warn!("lockscreen auth failed: {e}");
                self.state.authenticating = false;
                self.state.secret.clear();
                self.state.error = Some(rust_i18n::t!("auth_failed").to_string());
            }
        }
    }

    fn poll_external(&mut self) -> Vec<Msg> {
        let mut msgs = Vec::new();

        // Drive the slide toward its target (the subsurface reconcile reads
        // drag_progress every frame). On arrival: 1.0 reveals + focuses the
        // form; 0.0 returns to idle and clears the secret.
        if let Some(target) = self.anim_target {
            let dt = self.anim_last.elapsed().as_secs_f32();
            self.anim_last = Instant::now();
            let step = dt / REVEAL_SECS;
            let p = self.state.drag_progress;
            self.state.drag_progress = if target > p {
                (p + step).min(target)
            } else {
                (p - step).max(target)
            };
            if self.state.drag_progress == target {
                self.anim_target = None;
                if target >= 1.0 {
                    self.state.revealed = true;
                    self.pending_focus = true;
                } else {
                    self.state.revealed = false;
                    self.state.drag_progress = 0.0;
                    self.state.secret.clear();
                    self.state.error = None;
                }
            }
        }

        if self.last_tick.elapsed() >= Duration::from_secs(1) {
            self.last_tick = Instant::now();
            msgs.push(Msg::Tick);
        }

        if let Some(rx) = &self.auth_rx {
            match rx.try_recv() {
                Ok(r) => {
                    self.auth_rx = None;
                    msgs.push(Msg::AuthResult(r));
                }
                Err(std::sync::mpsc::TryRecvError::Empty) => {}
                Err(_) => {
                    self.auth_rx = None;
                    self.state.authenticating = false;
                }
            }
        }

        if self.fingerprint.poll() {
            msgs.push(Msg::AuthResult(Ok(())));
        }

        // One tick after the form is revealed (so its widgets are laid out),
        // focus password in lock mode or when a username is already present,
        // otherwise the username field.
        if self.pending_focus {
            self.pending_focus = false;
            self.focus_request = Some(
                if self.state.mode == Mode::Lock || !self.state.username.is_empty() {
                    ltk::WidgetId("password")
                } else {
                    ltk::WidgetId("username")
                },
            );
        }

        msgs
    }

    fn poll_interval(&self) -> Option<Duration> {
        Some(Duration::from_millis(if self.auth_rx.is_some() {
            50
        } else {
            1000
        }))
    }

    fn on_swipe_progress(&mut self, progress: f32) {
        if self.state.revealed {
            return;
        }
        if progress > 0.0 {
            // Following the finger; drop any in-flight return animation.
            self.anim_target = None;
            self.state.drag_progress = progress;
        } else if self.state.drag_progress > 0.0 && self.anim_target.is_none() {
            // Released below threshold — slide back down to idle.
            self.anim_target = Some(0.0);
            self.anim_last = Instant::now();
        }
    }

    fn on_swipe_up(&mut self) -> Option<Msg> {
        if !self.state.revealed {
            Some(Msg::Reveal)
        } else {
            None
        }
    }

    fn on_key(&mut self, keysym: ltk::Keysym) -> Option<Msg> {
        use ltk::Keysym;
        if !self.state.revealed
            && matches!(keysym, Keysym::Return | Keysym::KP_Enter | Keysym::space)
        {
            Some(Msg::Reveal)
        } else if self.state.revealed && keysym == Keysym::Escape {
            Some(Msg::ReturnToIdle)
        } else {
            None
        }
    }

    fn on_text_input_focused(&mut self, active: bool) {
        tracing::info!("lockscreen: on_text_input_focused( active={active} )");
        crate::osk::set_visible(active);
    }

    fn on_resize(&mut self, width: u32, height: u32) {
        self.state.surface_w = width;
        self.state.surface_h = height;
    }

    fn shell_mode(&self) -> ltk::ShellMode {
        match self.state.mode {
            Mode::Lock => ltk::ShellMode::SessionLock,
            Mode::Greeter => ltk::ShellMode::Layer(ltk::Layer::Top),
        }
    }

    fn requested_exit(&self) -> bool {
        self.exit
    }

    fn take_focus_request(&mut self) -> Option<ltk::WidgetId> {
        self.focus_request.take()
    }

    fn keyboard_exclusive(&self) -> bool {
        true
    }

    fn is_animating(&self) -> bool {
        self.anim_target.is_some()
    }
}
